## data gathering
PartA <- fread("psam_pusa.csv")
Data1 <- PartA %>% 
  filter(CIT %in% c(4,5), AGEP >= 20, AGEP < 70) %>% 
  select(PWGTP, POBP, WAOB, SEX, AGEP, ST, MAR, SCHL, FOD1P, FOD2P, INDP, 
         WKHP, PINCP, ADJINC, POVPIP, YOEP, HICOV, PRIVCOV, PUBCOV)
rm(PartA)

PartB <- fread(file = "psam_pusb.csv")
Data2 <- PartB %>% 
  filter(CIT %in% c(4,5), AGEP >= 20, AGEP < 70) %>% 
  select(PWGTP, POBP, WAOB, SEX, AGEP, ST, MAR, SCHL, FOD1P, FOD2P, INDP, 
         WKHP, PINCP, ADJINC, POVPIP, YOEP, HICOV, PRIVCOV, PUBCOV)
rm(PartB)

PartC <- fread(file = "psam_pusc.csv")
Data3 <- PartC %>% 
  filter(CIT %in% c(4,5), AGEP >= 20, AGEP < 70) %>% 
  select(PWGTP, POBP, WAOB, SEX, AGEP, ST, MAR, SCHL, FOD1P, FOD2P, INDP, 
         WKHP, PINCP, ADJINC, POVPIP, YOEP, HICOV, PRIVCOV, PUBCOV)
rm(PartC)

PartD <- fread(file = "psam_pusd.csv")
Data4 <- PartD %>% 
  filter(CIT %in% c(4,5), AGEP >= 20, AGEP < 70) %>% 
  select(PWGTP, POBP, WAOB, SEX, AGEP, ST, MAR, SCHL, FOD1P, FOD2P, INDP, 
         WKHP, PINCP, ADJINC, POVPIP, YOEP, HICOV, PRIVCOV, PUBCOV)
rm(PartD)

main.data <- bind_rows(Data1, Data2, Data3, Data4)
rm(Data1,Data2,Data3,Data4)

main.data <- as_tibble(main.data)
### Creating 2 datasets from PUMS Dictionary

pums_dic <- read_csv("CopyOfPUMS_Data_Dictionary_2015-2019.csv")

POBP <- pums_dic %>% 
  filter(PUMS_Variable_Name == "POBP") %>% 
  select(POB = Starting_Legal_Value, Description) %>% 
  slice(-1)

ST <- pums_dic %>% 
  filter(PUMS_Variable_Name == "ST") %>% 
  select(ST = Starting_Legal_Value, Description) %>% 
  slice(2:52) %>% 
  separate(Description, into = c("state_name", "state_abb"), sep = "/")
## factorizing the variables properly
colnames(main.data) <- c("weight", "birth_place", "birth_continent", "gender", 
                         "age", "resident_state", "marital_status", 
                         "educational_attainment", "first_degree", "second_degree",  
                         "industry", "worked_per_week", "total_income", 
                         "adjust_factor", "Income_to_poverty_ratio", "year_entry", 
                         "health_insurance", "private_health_insurance_bin",
                         "public_health_coverage_bin")
renamed.to <- colnames(main.data)

main.data <- main.data %>% mutate(year = case_when(
  adjust_factor == 1080470 ~ 2015,
  adjust_factor == 1073449 ~ 2016,
  adjust_factor == 1054606 ~ 2017,
  adjust_factor == 1031452 ~ 2018,
  TRUE ~ 2019
)) %>% 
  select(year, everything()) %>% 
  mutate(adj_income = round(total_income*(adjust_factor/1000000))) %>% 
  select(year:worked_per_week, adj_income, Income_to_poverty_ratio:public_health_coverage_bin, -adjust_factor, -total_income)


# Place of birth
POB_adj<- POBP %>% 
  filter( POB %in% unique(main.data$birth_place))
main.data$birth_place <- factor(main.data$birth_place, labels = POB_adj$Description)

# World area place of birth
main.data$birth_continent <- factor(main.data$birth_continent , labels = c("PR and US Island Areas","Latin America", "Asia", "Europe", "Africa", "Northern America", "Oceania and at Sea"))

main.data <- main.data %>% mutate(birth_continent = fct_recode(birth_continent,
    "North America"    = "PR and US Island Areas",
    "South America"      = "Latin America",
    "North America" = "Northern America",
    "Oceania and at Sea" = "Oceania and at Sea"
  ))

# Gender
main.data$gender <- factor(main.data$gender , labels = c("Male","Female"))

# State
main.data$resident_state <- factor(main.data$resident_state,labels = ST$state_abb)

# Marital status 
main.data$marital_status <- factor(main.data$marital_status , labels = c("Married","Widowed", "Divorced", "Separated", "Never married or under 15 years old"))

# Educational attainment
main.data$educational_attainment <- factor(main.data$educational_attainment)

main.data <- main.data %>%
  mutate(educational_attainment = fct_collapse(educational_attainment,
    "No diploma" = as.character(rep(01:15)),
    "Diploma with no degree" = as.character(rep(16:19)),
    "Associate's degree" = as.character(20),
    "Beyond an Associate's degree" = as.character(rep(21:24))
    ))

# First degree
main.data$first_degree <- as.factor(main.data$first_degree)

main.data <- main.data %>%
  mutate(first_degree = fct_collapse(first_degree,
    `Computers, Mathematics and Statistics` = as.character(c(rep(2001:2107),rep(3700:3702),4005)),
    `Biological Agricultural Environmental Sciences` = as.character(c(rep(1100:1303),rep(3600:3699))),
    `Physical and Related Sciences` = as.character(rep(5000:5009)),
    Psychology = as.character(rep(5200:5299)),
    `Social Sciences` = as.character(c(2901,5301,rep(5500:5599))),
    Engineering = as.character(rep(2400:2502)),
    `Multidisciplinary Studies` = as.character(c(rep(4000:4002),4006,4007,5098)),
    `Science and Engineering Related` = as.character(c(rep(2503:2599),5701,rep(6100:6199))),
    Business = as.character(c(rep(6200:6206),rep(6209:6299))),
    Finance = as.character(6207),
    Education = as.character(rep(2300:2399)),
    `Literature and Languages` = as.character(c(rep(2601:2603),3301,3302)),
    `Liberal Arts and History` = as.character(c(rep(3401:3501),4801,4901,6402,6403)),
    `Visual and Performing Arts` = as.character(c(1401,1501,rep(6000:6099),6002)),
    Communications = as.character(rep(1901:1904)),
    Other = as.character(c(2201,3201,3202,3801,4101,5102,rep(5401:5404),5601,5901))))

### Replace NA value in first_degree and replace it with educational_attainment
main.data <- main.data %>% 
    mutate(first_degree = coalesce(first_degree, educational_attainment)) %>% 
  select(-educational_attainment) %>% 
  rename(education = first_degree)

# Second degree
main.data$second_degree <- as.factor(main.data$second_degree)

main.data <- main.data %>%
  mutate(second_degree = fct_collapse(second_degree,
    `Computers, Mathematics and Statistics` = as.character(c(rep(2001:2107),rep(3700:3702),4005)),
    `Biological Agricultural Environmental Sciences` = as.character(c(rep(1100:1303),rep(3600:3699))),
    `Physical and Related Sciences` = as.character(rep(5000:5009)),
    Psychology = as.character(rep(5200:5299)),
    `Social Sciences` = as.character(c(2901,5301,rep(5500:5599))),
    Engineering = as.character(rep(2400:2502)),
    `Multidisciplinary Studies` = as.character(c(rep(4000:4002),4006,4007,5098)),
    `Science and Engineering Related` = as.character(c(rep(2503:2599),5701,rep(6100:6199))),
    Business = as.character(c(rep(6200:6206),rep(6209:6299))),
    Finance = as.character(6207),
    Education = as.character(rep(2300:2399)),
    `Literature and Languages` = as.character(c(rep(2601:2603),3301,3302)),
    `Liberal Arts and History` = as.character(c(rep(3401:3501),4801,4901,6402,6403)),
    `Visual and Performing Arts` = as.character(c(1401,1501,rep(6000:6099),6002)),
    Communications = as.character(rep(1901:1904)),
    Other = as.character(c(2201,3201,3202,3801,4101,5102,rep(5401:5404),5601,5901))))

# Industry
main.data$industry <- as.factor(main.data$industry)

main.data <- main.data %>%
  mutate(industry = fct_collapse(industry,
    "Agriculture, Forestry, Fishing, and Hunting" = as.character(rep(0170:0290)),
    "Mining, Quarrying, and Oil and Gas Extraction" = as.character(rep(0370:0490)),
    Utilities = as.character(rep(0570:0690)),
    Construction = as.character(0770),
    Manufacturing = as.character(rep(1070:3990)),
    "Wholesale Trade"= as.character(rep(4070:4590)),
    "Retail Trade" = as.character(rep(4670:5790)),
    "Transportation and Warehousing" = as.character(rep(6070:6390)),
    "Information" = as.character(rep(6470:6780)),
    "Finance and Insurance" = as.character(rep(6870:6992)),
    "Real Estate and Rental and Leasing" = as.character(rep(7071:7190)),
    "Professional, Scientific, and Technical Services" = as.character(rep(7270:7490)),
    "Management of companies and enterprises" = as.character(7570),
    "Administrative and support and waste management services" = as.character(rep(7580:7790)),
    "Educational Services" = as.character(rep(7860:7890)),
    "Health Care and Social Assistance" = as.character(rep(7970:8470)),
    "Arts, Entertainment, and Recreation" = as.character(rep(8561:8590)),
    "Accommodation and Food Services" = as.character(rep(8660:8690)),
    "Other Services, Except Public Administration" = as.character(rep(8770:9290)),
    "Public Administration" = as.character(rep(9370:9590)),
    "Military" = as.character(rep(9670:9870)),
    "Unemployed in 5 years" = as.character(9920)
    ))

# Health insurance coverage?
main.data$health_insurance <- factor(main.data$health_insurance, labels = c("Yes","No"))

# Private health insurance coverage?
main.data$private_health_insurance_bin <- factor(main.data$private_health_insurance_bin, labels = c("Yes","No"))

# Public health coverage?
main.data$public_health_coverage_bin <- factor(main.data$public_health_coverage_bin, labels = c("Yes","No"))

# Convert Health insurance to three values
main.data <- main.data %>% 
  mutate(health_insurance = case_when(
    public_health_coverage_bin == "Yes" ~ "Public",
    private_health_insurance_bin == "Yes" ~ "Private",
    TRUE ~ "No Insurance"
)) %>% select(-private_health_insurance_bin, -public_health_coverage_bin)

main.data$health_insurance <- as.factor(main.data$health_insurance)

save(main.data ,file = "tidy.data.RData")

Data summary:

# Load the data
load("tidy.data.RData")

The data I am working on is from the 2019 American Community Survey (ACS) 5-year Public Use Microdata Samples (PUMS), a sample of the actual responses collected by the American Community Survey between 2015-2019 split into the population and household characteristics. I have selected population data, and each record represents a population sample. This data contains a weighting value to account for the fact that individuals are not sampled with equal probability (people who have a greater chance of being sampled have a lower weight to reflect this). These are essentially a frequency count for each row.

I filter out US-born people and the individuals whose parents are American. In other words, I narrowed down the data to the people who were born outside the United States with non-US parents. Moreover, I filtered my data to people between 20 and 70 years old, not including 70. There were about 200 variables in the original dataset. I picked 19 variables at first.

These variables are:

original.column.name <- c("PWGTP", "POBP", "WAOB", "SEX", "AGEP", "ST", "MAR", "SCHL", 
               "FOD1P", "FOD2P", "INDP", "WKHP", "PINCP", "ADJINC", "POVPIP", 
               "YOEP", "HICOV", "PRIVCOV", "PUBCOV")

renamed.to <- c("weight", "birth_place", "birth_continent", "gender", 
                         "age", "resident_state", "marital_status", 
                         "educational_attainment", "first_degree", "second_degree",  
                         "industry", "worked_per_week", "total_income", 
                         "adjust_factor", "Income_to_poverty_ratio", "year_entry", 
                         "health_insurance", "private_health_insurance_bin",
                         "public_health_coverage_bin")
kable(data.frame(original.column.name, renamed.to))
original.column.name renamed.to
PWGTP weight
POBP birth_place
WAOB birth_continent
SEX gender
AGEP age
ST resident_state
MAR marital_status
SCHL educational_attainment
FOD1P first_degree
FOD2P second_degree
INDP industry
WKHP worked_per_week
PINCP total_income
ADJINC adjust_factor
POVPIP Income_to_poverty_ratio
YOEP year_entry
HICOV health_insurance
PRIVCOV private_health_insurance_bin
PUBCOV public_health_coverage_bin
NA

It is worth mentioning that only three variables under for report are top-coded, age, total_income and income to poverty ratio. The top code threshold of variable age is below 91, which is not important since I filtered my data to the age below 70. Only in analyzing the total_income variable the result might be affected. The top-coded threshold for this variable is $4,209,995. The top coded for income to poverty ratio is 0.501.

I searched through the variable values in the “2015-2019 ACS PUMS Data Dictionary” file to see how I could pre-process my variables in the best way possible. First, mutated a new column named “year” based on adjust_factor. Then I changed total_income to the adj_income variable by adjusting for inflation for each year. Some variables needed more time and effort to allocate them proper factor label. For instance, I labelled educational_attainment to four factors. ACS classified the field of degree into 15 categories. I was curious about the field of Finance as well. So, I divided all fields into 16 fields. Then, I combined the educational_attainment with the first_degree column to education column. The industry column has more than 200 values. Based on the “2017-industry-code-list-with-crosswalk” on https://www.census.gov, I recoded them to 22 levels. Health Insurance had three separate columns. I combined all of them into one column and allocated three values “private”, “public” and “no insurance”. I changed the type of all other variables to factor and saved the file to “tidy.data.RData”. Here is the summary of data after pre-processing.

# Numerical data
kable(summary(main.data[,c(1,2,6,12,13,14,15)]), caption = "Numerical data")
Numerical data
year weight age worked_per_week adj_income Income_to_poverty_ratio year_entry
Min. :2015 Min. : 1.00 Min. :20.00 Min. : 1.0 Min. : -10805 Min. : 0.0 Min. :1945
1st Qu.:2016 1st Qu.: 13.00 1st Qu.:35.00 1st Qu.:38.0 1st Qu.: 8373 1st Qu.:161.0 1st Qu.:1986
Median :2017 Median : 18.00 Median :45.00 Median :40.0 Median : 25931 Median :308.0 Median :1997
Mean :2017 Mean : 24.05 Mean :45.17 Mean :39.3 Mean : 45710 Mean :308.2 Mean :1995
3rd Qu.:2018 3rd Qu.: 29.00 3rd Qu.:56.00 3rd Qu.:40.0 3rd Qu.: 54840 3rd Qu.:501.0 3rd Qu.:2006
Max. :2019 Max. :435.00 Max. :69.00 Max. :99.0 Max. :1788178 Max. :501.0 Max. :2019
NA NA NA NA’s :375611 NA NA’s :27280 NA
# Categorical data
kable(summary(main.data[,c(3,4,5,7,8,9,10,11,16)]), caption = "Categorical data")
Categorical data
birth_place birth_continent gender resident_state marital_status education second_degree industry health_insurance
Mexico :375367 North America : 32299 Male :722629 CA :403311 Married :996025 Diploma with no degree :531732 Business : 7573 Health Care and Social Assistance :166880 No Insurance:281363
India : 93119 South America :717940 Female:782830 TX :161777 Widowed : 34091 No diploma :355688 Social Sciences : 5511 Manufacturing :138755 Private :894430
China : 80154 Asia :511219 NA NY :152675 Divorced :122871 Associate’s degree : 99209 Computers, Mathematics and Statistics: 5170 Retail Trade :111792 Public :329666
Philippines: 77034 Europe :167965 NA FL :140039 Separated : 39282 Business : 93306 Engineering : 4650 Accommodation and Food Services :106426 NA
Vietnam : 53096 Africa : 66454 NA NJ : 71127 Never married or under 15 years old:313190 Engineering : 89739 Literature and Languages : 3216 Professional, Scientific, and Technical Services: 99144 NA
El Salvador: 43637 Oceania and at Sea: 9582 NA IL : 57412 NA Computers, Mathematics and Statistics: 46379 (Other) : 22468 (Other) :623710 NA
(Other) :783052 NA NA (Other):519118 NA (Other) :289406 NA’s :1456871 NA’s :258752 NA

This summary has some interesting findings. worked_per_week variable has the same value for median and 3rd quarter. I will explore this variable in the next section. Mexico has the most population of Immigrants following by India and China. I will also elaborate on the distribution of birthplace across the States. About 1456871 missing values for second degree showed at least 48588 observations have a second degree or beyond. At last, 894430 observations have private insurance followed by 329666 public insurance observations.

main.data %>%
  filter(!is.na(industry), 
         !industry == "Unemployed in 5 years", 
         adj_income < 500000) %>% 
  ggplot() +
  aes(adj_income) +
  geom_density() + 
  scale_y_continuous(name="Frequency", labels = scales::percent) +
  scale_x_continuous(name="Adjusted income", labels = scales::comma)

This diagram is a right-skewed distribution of adjusted income less than $500,000 working in the industry. Most immigrants have a salary of fewer than 100,000 dollars.

miu <- main.data %>% 
  filter(!is.na(worked_per_week)) %>% 
  summarise(Avg.hour.worked = wtd.mean(worked_per_week, weights = weight, normwt = F))

# number of people with 40 hour work time

work.fourty <- main.data %>% 
    group_by(worked_per_week) %>% 
    summarise(count = sum(weight)) %>% 
  filter(worked_per_week == 40) %>% 
  ungroup() %>% 
  select(count) %>% 
  as.numeric()

worked_per_week should follow a Poisson distribution. I applied the weight of each observation to get an accurate result. The weighted mean is 39.42, indicating that many people have a typical 40-hour work time. The following distribution shows 14745983 people worked exactly 40 hours per week.

main.data %>% 
  group_by(worked_per_week) %>% 
  summarise(count = sum(weight)) %>% 
  ggplot() +
  aes (worked_per_week, count) +
  geom_histogram(stat = "identity") +
  xlab("Hour worked per week")

NA
NA
NA

It is exciting to extract some information about the immigrant’s demographics. How were the immigrants distributed from 2015 to 2019 across the US? What countries have the most population for each state?


max_migrate <- main.data %>% 
  group_by(resident_state) %>% 
  summarise(population = sum(weight)) 

library(usmap)
# Non-US-born population by state
max_migrate$fips <- statepop$fips
max_migrate$abbr <- statepop$abbr

plot_usmap(data = max_migrate, values = "population", alpha = .9, labels = T) + 
  scale_fill_continuous(low = "white", 
                        high = "blue", 
                        name = "Non-US-born population", 
                        label = scales::comma) + 
  labs(title = "Non-US-born population by state") +
  theme(legend.position = "right")

The above US map showed which states are the most populated immigrants. I applied a population logarithm to see which state had the lowest non-US-born people. The below map shows these states’ populations more precisely. The CA has the most population among all states, with 8774764 immigrants.

# Log population of non-US-born by state
max_migrate_log <- max_migrate %>% 
  mutate(log_pop = log(population))

plot_usmap(data = max_migrate_log, values = "log_pop", alpha = .9, labels = T) + 
  scale_fill_continuous(low = "white", 
                        high = "blue", 
                        name = "Non-US-born log population", 
                        label = scales::comma) + 
  labs(title = "Non-US-born log population by state") +
  theme(legend.position = "right")

# What non-US country has the most population for each state?
max_pop <- main.data %>% 
  group_by(resident_state, birth_place) %>% 
  summarise(population = sum(weight)) %>% 
  ungroup(birth_place) %>% 
  slice(which.max(population))
`summarise()` has grouped output by 'resident_state'. You can override using the `.groups` argument.

The below map shows what non-US country has the most population for each state. In 33 states, people born in Mexico have the most population.

max_pop$fips <- statepop$fips
max_pop$abbr <- statepop$abbr

plot_usmap(data = max_pop, values = "birth_place", alpha = .7, labels = T) + 
  scale_fill_discrete(name = "Country")  +
  labs(title = "What non-US country has the most population for each state?") +
  theme(legend.position = "right")

main.data %>% 
  select(weight, year_entry) %>% 
  group_by(year_entry) %>% 
  summarise(Population = sum(weight)) %>% 
  ggplot() +
  aes(year_entry, Population) +
  geom_point() +
  geom_smooth() +
  labs(x = "Year of Entry", title = "Number of non-US born people between 1945-2019" )
`geom_smooth()` using method = 'loess' and formula 'y ~ x'

As we can see from the above graph, the number of non_US born people have surged till 2000, and we faced the decrease upon that time.

The chart below shows the relationship between the average income adjusted for inflation with the continent they were born in. South Africa has the lowest average income, and North America has the most revenue.

incom.continent <- main.data %>% 
  select(weight, birth_continent, adj_income, Income_to_poverty_ratio) %>% 
  group_by(birth_continent) %>% 
  summarise(adj_income = wtd.mean(adj_income, weights = weight, normwt = F)) %>% 
  arrange(desc(adj_income))

incom.continent %>% 
  ggplot() +
  aes(fct_reorder(birth_continent, adj_income), adj_income, fill = birth_continent) +
  geom_bar(stat = "identity") +
  theme(legend.position = "none") +
  labs(x = "Birth continent", y = "Income adjusted for inflation")

top.income <- main.data %>% 
  select(weight, birth_place, adj_income, Income_to_poverty_ratio) %>% 
  group_by(birth_place) %>% 
  summarise(adj_income = wtd.mean(adj_income, weights = weight, normwt = F)) %>% 
              arrange(desc(adj_income)) %>% 
  head(20) 
top.income %>%
  kable(caption = "Top 20 average income generated by non-US born people")
Top 20 average income generated by non-US born people
birth_place adj_income
Australia 98872.44
United Kingdom, Not Specified 98464.19
Denmark 92531.04
Norway 92496.68
New Zealand 91134.54
Belgium 90607.44
Switzerland 89851.28
France 89541.11
Cyprus (2016 or earlier) 86629.92
Ireland 86154.50
Sweden 84780.43
South Africa 83196.75
Netherlands 82424.83
Israel 79757.13
Northern Ireland (2017 or later) 79495.38
Scotland 79202.09
Canada 78019.14
India 76507.41
England 75769.74
Finland 75033.58

Australian people have the most average income, with 98872 annually. As seen in the table, British and Scandinavians are high earner in the US.

foreign_GDP <- main.data %>% 
  select(weight, birth_place, adj_income, Income_to_poverty_ratio) %>% 
  group_by(birth_place) %>% 
  summarise(adj_income = wtd.mean(adj_income, weights = weight, normwt = F), 
            population = sum(weight), GDP = adj_income*population/10^(9)) %>% 
  arrange(desc(GDP)) %>% 
  head(20)
kable(foreign_GDP, caption = "Top 20 US GDP generated by non-US born people")
Top 20 US GDP generated by non-US born people
birth_place adj_income population GDP
Mexico 26318.78 9915559 260.96542
India 76507.41 2166665 165.76593
China 48921.34 1687009 82.53074
Philippines 47508.27 1575722 74.85983
Vietnam 41094.32 1126421 46.28950
Canada 78019.14 586791 45.78093
Korea 50835.20 840813 42.74289
El Salvador 28564.93 1202845 34.35918
Cuba 33722.54 948854 31.99777
Dominican Republic 28747.93 911435 26.20187
Jamaica 41395.09 606358 25.10024
Colombia 38277.43 634281 24.27865
United Kingdom, Not Specified 98464.19 246013 24.22347
Taiwan 69488.73 328480 22.82566
Guatemala 25652.17 827511 21.22745
Germany 63858.83 330086 21.07891
Iran 67296.37 309422 20.82298
Haiti 32476.89 548788 17.82293
Poland 50765.12 337055 17.11064
Brazil 44749.15 380576 17.03045
NA

By multiplying adjusted income and population for each country, I created a new column, GDP, which stands for Gross Domestic Period. Mexican with 261 billion dollars contributed the most among all nations for generating US GDP since they have the most population in the US, followed by India and China.

asian_GDP <- main.data %>% 
  filter(birth_continent == "Asia") %>% 
  select(weight, birth_place, adj_income, Income_to_poverty_ratio) %>% 
  group_by(birth_place) %>% 
  summarise(adj_income = wtd.mean(adj_income, weights = weight, normwt = F), 
            population = sum(weight), GDP =adj_income*population/10^(9)) %>% 
  arrange(desc(GDP)) %>% 
  head(20)
kable(asian_GDP, caption = "Top 20 US GDP generated by Asian born people")
Top 20 US GDP generated by Asian born people
birth_place adj_income population GDP
India 76507.41 2166665 165.765927
China 48921.34 1687009 82.530739
Philippines 47508.27 1575722 74.859832
Vietnam 41094.32 1126421 46.289500
Korea 50835.20 840813 42.742895
Taiwan 69488.73 328480 22.825658
Iran 67296.37 309422 20.822977
Pakistan 49771.30 333710 16.609181
Hong Kong 69935.64 204079 14.272395
Japan 53002.98 254688 13.499223
Israel 79757.13 110215 8.790432
Bangladesh 36264.30 206676 7.494960
Thailand 35359.25 202086 7.145609
Lebanon 67323.06 101481 6.832012
Turkey 60557.66 101329 6.136248
Laos 34637.99 161005 5.576890
Iraq 30160.58 168077 5.069300
Cambodia 34792.89 127681 4.442392
Nepal 36182.85 111481 4.033700
Malaysia 64671.63 60200 3.893232

The above table is the same data but filtered down to Asia. The share of India for generating GDP with 166 billion dollars in the US is more than twice of the second country, China with 83 billion dollars.

poverty.ratio <- main.data %>% 
  select(weight, birth_place, adj_income, Income_to_poverty_ratio) %>% 
  group_by(birth_place) %>% 
  summarise(Income_to_poverty_ratio = wtd.mean(Income_to_poverty_ratio, weights = weight, normwt = F), 
            population = sum(weight)) %>% 
  arrange(desc(Income_to_poverty_ratio)) %>% 
  head(20)

The table below shows that the most earner people have the best income to poverty ratio. The United Kingdom is the first country on this list with an income to poverty ratio of 417.

kable(poverty.ratio, caption = "Top 20 countries in terms of    income to poverty ratio")
Top 20 countries in terms of income to poverty ratio
birth_place Income_to_poverty_ratio population
United Kingdom, Not Specified 416.8702 246013
Australia 410.9262 72603
Ireland 408.9320 80573
India 406.1954 2166665
Northern Ireland (2017 or later) 405.2358 5494
Denmark 399.4487 18490
Switzerland 395.1994 24666
Netherlands 394.8949 55569
France 394.3146 134708
Belgium 392.6630 23777
New Zealand 392.5880 24289
South Africa 392.3358 87345
Scotland 391.6818 32528
USSR 391.4027 47711
Hong Kong 391.1238 204079
Canada 389.4602 586791
Sweden 388.7042 34173
England 387.8261 206758
Finland 385.6493 14049
Taiwan 385.1985 328480
NA
health_insurance <- main.data %>% 
  group_by(health_insurance) %>% 
  summarise(count = sum(weight))

About 56% of immigrants have private insurance and 21% have public insurance, and the rest have no insurance. The below chart shows this distribution in a bar chart diagram.

health_insurance %>% 
  ggplot() +
  aes(health_insurance, count, fill = health_insurance) +
  geom_bar(stat = "identity") +
  theme(legend.position = "none") +
  xlab("Health Insurance")

Methodology

In this project, I investigated two different studies in my data.

1- I tried regressing adjusted income on immigrants’ age and gender.

2- Which type of health insurance, i.e., public, private or without insurance, is suitable for what class of immigrants?

Each observation had a weight assigned to it. In all my analyses, I consider weight to get an accurate result of my data. I also applied the adjusted factor into the total income variable to reflect the effect of inflation from 2015 to 2019. I included outliers in my analysis to avoid missing any explanation power. Moreover, I removed all the observations with missing values in my research to get a relevant result. I needed to check the correlations between the numeric data for the collinearity issues.

For the first study, I averaged all the income data by age and gender to easily see the data trend. I examined whether, by adding gender, our model would improve in fit or not. Then, I studied the effect of the interaction of age and gender on adjusted income and whether the improvement in model fit is worth the increased complexity of our model. Does adding the interaction term provide additional explanatory power over the simpler model? The data has an apparent nonlinearity, so I considered the second-order of age in my model. I compared all my model’s AIC to pick the lowest AIC as the best model.

For the second study, I examined how the relationship between health insurance with other variables is in my data. I applied a chi-square test to see any relationship between the health insurance type and the continent where immigrants were born. In addition, I modelled a multiple logistic regression to predict the model with the variables. The first model was complex, and it took so much time to build a model. To simplify my model, I tried to aggregate education and industry variables with many values into an appropriate number of clusters. I implemented k-mean clustering, elbow, and Silhouette analysis to find the best k (number of clusters). Then I generated a multiple logistic regression with the simplified model.

Findings

Correlation

# Correlation matrix: 
cor.data <- main.data %>% 
  select(year, age, worked_per_week, adj_income, 
         Income_to_poverty_ratio, year_entry)
cor.mat <- matrix(0,length(cor.data),length(cor.data))
for (i in 1:length(cor.data)) {
  for (j in 1:length(cor.data)) {
    cor.mat[i,j] = wtd.cor(cor.data[,i], cor.data[,j], weight = main.data$weight)[1]
    
  }
}

colnames(cor.mat) <- c("Year", "Age", "Hour worked/week", "Adjusted income", 
         "Income to poverty ratio", "Year of entry")

rownames(cor.mat) <- c("Year", "Age", "Hour worked/week", "Adjusted income", 
         "Income to poverty ratio", "Year of entry")

kable(cor.mat)
Year Age Hour worked/week Adjusted income Income to poverty ratio Year of entry
Year 1.0000000 0.0265495 0.0044760 0.0330315 0.0601947 0.0786202
Age 0.0265495 1.0000000 0.0348378 0.0790131 0.1412132 -0.5560814
Hour worked/week 0.0044760 0.0348378 1.0000000 0.2680177 0.2035691 -0.0427606
Adjusted income 0.0330315 0.0790131 0.2680177 1.0000000 0.4711922 -0.1017869
Income to poverty ratio 0.0601947 0.1412132 0.2035691 0.4711922 1.0000000 -0.1494788
Year of entry 0.0786202 -0.5560814 -0.0427606 -0.1017869 -0.1494788 1.0000000

This correlation matrix shows all the correlations between the numerical variables. As seen, the variable “year of the entry” negatively correlates with most of the variables. “income to poverty ratio” and “adjusted income” variables are about 0.47 correlated, which is the most highest correlation between all the study variables. In addition, the heat map visualizes the weighted correlation between the selected numerical variables in a more beautiful format.

corrplot(cor.mat, method="color")

Income ~ age, gender

# weighted t-test
male.income <- main.data %>% 
  filter(gender == "Male") %>% 
  select(weight, adj_income)

female.income <- main.data %>% 
  filter(gender == "Female") %>% 
  select(weight, adj_income)

t_test_salary <- wtd.t.test(male.income$adj_income, 
           female.income$adj_income, 
           male.income$weight, 
           female.income$weight, 
           mean1 = FALSE)

I examined the weighted t-test to see is there any statistical difference in average salary between men and women. The average men’s salary is $55,113 and $29,969 women. In immigrant people, we can conclude that men and women have statistically significantly different average wages (t-stats = 0).

To investigate more about the average salary between immigrants, I added age as a new variable to show the income distribution across the age as well as gender.

avg.income.data <- main.data %>% 
  group_by(age, gender) %>% 
  summarise("Average income" = wtd.mean(adj_income, 
                                        weights = weight, 
                                        normwt = FALSE))
`summarise()` has grouped output by 'age'. You can override using the `.groups` argument.
avg.income.data %>% 
  ggplot() + 
  aes(age, `Average income`, color = gender) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE)
`geom_smooth()` using formula 'y ~ x'

The above graph shows the scatter plot of immigrants’ average salary by age and gender. Salaries rose until the age 45 to 50 and then we see a decline in wage after that age. As seen from the graph, men have on average more salary than women in every age group.

age.model <- lm(`Average income` ~ age, avg.income.data)
summary.age <- summary(age.model)
AIC1 <- AIC(age.model)

First, I built a model of income regressed by age. The Adjusted R-squared is 0.127 which is very low. The AIC is 2225. I plotted the residual values to see which issues we had in terms of normality, conditional heteroskedasticity and effect of outliers in our study. As seen, the results are not very promising.

par(mfrow=c(2,2))
plot(age.model)

gender.age.model <- lm(`Average income` ~ gender + age -1, avg.income.data)
summary.gender <- summary(gender.age.model)
AIC2 <- AIC(gender.age.model)
compare.gender.added <- anova(age.model, gender.age.model)

I added gender to see if our model would improve in fit or not. I used ANOVA to compare the previous model to this model. This output tells us that the gender variable is statistically significant. In other words, it is unlikely that the improvement in fit when adding the gender variable is simply due to random fluctuations in the data F = 132.45. Also, the AIC for this model was 2141, which was less than the previous one. At last, I checked the residuals, and as it is seen, only the Normal Q-Q plot has enhanced.

par(mfrow=c(2,2))
plot(gender.age.model)

interact.model <- lm(`Average income` ~ gender * age, avg.income.data)
summary.interact <- summary(interact.model)
AIC3 <- AIC(interact.model)
compare.interaction <- anova(gender.age.model, interact.model)
p_value_interact <- compare.interaction$`Pr(>F)`[2]

Then, I added the effect of the interaction of age and gender on salary and whether the improvement in model fit is worth the increased complexity of our model. ANOVA test tells us that adding interaction effect is statistically significant, F = 7.9, p-value = 0.006. The AIC for this model was 2135, slightly less than the previous one. To see if the conditional heteroskedasticity improved, I plotted residuals and it shows no improvement.

par(mfrow=c(2,2))
plot(interact.model)


poly.model <- lm(`Average income` ~  gender * poly(age, 2), avg.income.data)

poly.model.summary <- summary(poly.model)
AIC4 <- AIC(poly.model)

The data has an evident nonlinearity, so I examined the second-order polynomial of age with the interaction effect by gender. Here are the coefficients for this model:

poly.model.rowname <- c("Intercept", "genderFemale",  "age", "age^2", 
                        "interact(age,gender)", "interact(age^2,gender)")
rownames(poly.model.summary$coefficients) <- poly.model.rowname
kable(poly.model.summary$coefficients, digits = c(2,2,2,2), format = "markdown" )
Estimate Std. Error t value Pr(>|t|)
Intercept 52757.45 207.96 253.69 0
genderFemale -24166.09 294.10 -82.17 0
age 91457.62 2079.60 43.98 0
age^2 -125115.80 2079.60 -60.16 0
interact(age,gender) -57033.44 2941.00 -19.39 0
interact(age^2,gender) 64306.03 2941.00 21.87 0
compare.poly <- anova(poly.model, interact.model)
p_value_poly <- compare.poly$`Pr(>F)`[2]

The AIC for this model was 1750, much less than the previous AIC’s. ANOVA test tells us adding age^2 can statistically change our model , F = 2237.34, p-value = 5.3e-80. From the distribution of the residuals, we can see that we have no more conditional heteroskedasticity. In the residual vs leverage plot, only observation No. 99 has a standard residual greater than 3, considered an outlier. In this plot, the dotted red lines are Cook’s distance, and the areas of interest for us are the ones outside the dotted line on the top right corner (and possibly the lower right). The No. 99 observation is close to this dotted red line.

par(mfrow=c(2,2))
plot(poly.model)

The below graph shows the average immigrants’ average salaries by age and gender. Points are the actual averaged income data, and the lines are the polynomial model that I have built. It is fascinating to see this data followed the model very smoothly.

new.df <- data.frame(gender = avg.income.data$gender, age = avg.income.data$age)
new.df$pred.inc <- predict(object = poly.model, newdata = new.df)
new.df %>% 
  ggplot() +
  aes(age, pred.inc, color = gender) + 
  geom_line() +
  geom_point(data = avg.income.data, aes(x = age, y = `Average income`, color = gender))

Multiple logistic regression

chi-square test

library(weights)

healthcare1 <- main.data %>% 
  select(weight, birth_continent, health_insurance)

chi.health <- wtd.chi.sq(var1 = healthcare1$birth_continent, var2 = healthcare1$health_insurance, weight = healthcare1$weight, mean1=F)

I examined the weighted chi-squared test to see any relationship between the type of health insurance and the continent where immigrants were born. The result showed a significant difference between where they were born and health insurance. Chi.sq = 3.3840538^{6} and p-value = 0.

Complex model

# preparing data
big.model <- main.data %>% 
  select(weight, birth_continent,  gender, age, education, industry, 
         worked_per_week, adj_income, health_insurance) 

To investigate the relationship between the type of health insurance and other relevant variables in this dataset. I generated a multiple logistic regression model and included birth_continent, gender, age, education, industry, worked_per_week and adj_income to describe the health_insurance type. I considered weighted data in my analysis to get an actual result. It is worth mentioning that I did not consider income_to_poverty_ratio since it has a positive correlation r = 0.47 with adj_income, and it might cause collinearity.

# summarizing data

kable(summary(big.model), caption = "Logistic regression variables")
Logistic regression variables
weight birth_continent gender age education industry worked_per_week adj_income health_insurance
Min. : 1.00 North America : 32299 Male :722629 Min. :20.00 Diploma with no degree :531732 Health Care and Social Assistance :166880 Min. : 1.0 Min. : -10805 No Insurance:281363
1st Qu.: 13.00 South America :717940 Female:782830 1st Qu.:35.00 No diploma :355688 Manufacturing :138755 1st Qu.:38.0 1st Qu.: 8373 Private :894430
Median : 18.00 Asia :511219 NA Median :45.00 Associate’s degree : 99209 Retail Trade :111792 Median :40.0 Median : 25931 Public :329666
Mean : 24.05 Europe :167965 NA Mean :45.17 Business : 93306 Accommodation and Food Services :106426 Mean :39.3 Mean : 45710 NA
3rd Qu.: 29.00 Africa : 66454 NA 3rd Qu.:56.00 Engineering : 89739 Professional, Scientific, and Technical Services: 99144 3rd Qu.:40.0 3rd Qu.: 54840 NA
Max. :435.00 Oceania and at Sea: 9582 NA Max. :69.00 Computers, Mathematics and Statistics: 46379 (Other) :623710 Max. :99.0 Max. :1788178 NA
NA NA NA NA (Other) :289406 NA’s :258752 NA’s :375611 NA NA

Dealing with NAs

# Filtered out missing observations
big.model <- big.model %>% 
  filter(!is.na(worked_per_week), !is.na(industry))

To get a relevant result, I removed observations with missing values.

Train and Test dataset

To check the accuracy of my model, I divided the data into train and test datasets with a 70:30 ratio, respectively. The tables below show the proportion of each dataset, indicating that those random samplings have the same proportion for each type of health insurance.

big.model <- big.model %>% 
  mutate(id = row_number())
train1.data <- big.model %>% sample_frac(.70)
test1.data <-anti_join(big.model, train1.data, by='id')

train <- prop.table(wtd.table(train1.data$health_insurance, weights = train1.data$weight, type = "table"))
kable(train, col.names = "proportion", caption = "Train dataset proportion", format = "markdown")
Train dataset proportion
proportion
No Insurance 0.2187271
Private 0.6339445
Public 0.1473283
test <- prop.table(wtd.table(test1.data$health_insurance, weights = test1.data$weight, type = "table"))
kable(test, col.names = "proportion", caption = "Test dataset proportion", format = "markdown")
Test dataset proportion
proportion
No Insurance 0.2182591
Private 0.6325066
Public 0.1492343
NA

Build the complex model

time1 <- system.time(multinom.fit1 <- multinom(health_insurance ~ . -id -weight, maxit=500, data = train1.data, weights = weight))
# weights:  153 (100 variable)
initial  value 21109926.311568 
iter  10 value 16012451.903361
iter  20 value 15871906.019698
iter  30 value 15860553.301948
iter  40 value 15852488.904856
iter  50 value 15843526.418016
iter  60 value 15769655.031467
iter  70 value 15099056.928641
iter  80 value 14652298.399489
iter  90 value 14608827.055858
iter 100 value 14413123.397616
iter 110 value 14366042.531065
final  value 14366022.612617 
converged
summary.big.model <- summary(multinom.fit1)
p1.big<-predict(multinom.fit1, type="class", newdata=test1.data)
eval.big.model<- postResample(test1.data$health_insurance, p1.big)

education and industry had 20 and 22 values, respectively. R took too much time (t=337s) to generate the model. 49 different variables made the model complex. However, the model accuracy was 0.7057; the model efficiency was not good enough because it had too many variables and took several minutes to build the model. In addition, the importance of each variable is shown in the table below.

# The importance of different predictors
imp.big.model <- varImp(multinom.fit1)
imp.big.model <- imp.big.model %>% 
  arrange(desc(Overall))
kable(imp.big.model, digits = 2)
Overall
industryMilitary 7.34
industryPublic Administration 2.44
birth_continentSouth America 2.01
industryEducational Services 1.85
industryMining, Quarrying, and Oil and Gas Extraction 1.78
industryManagement of companies and enterprises 1.52
industryUtilities 1.39
industryFinance and Insurance 1.37
educationNo diploma 1.36
industryHealth Care and Social Assistance 1.34
industryConstruction 1.18
industryInformation 1.18
industryManufacturing 1.15
industryProfessional, Scientific, and Technical Services 1.14
industryArts, Entertainment, and Recreation 0.96
industryReal Estate and Rental and Leasing 0.93
industryWholesale Trade 0.90
birth_continentAfrica 0.83
educationDiploma with no degree 0.71
birth_continentOceania and at Sea 0.70
industryRetail Trade 0.65
educationEducation 0.60
genderFemale 0.56
educationVisual and Performing Arts 0.49
industryTransportation and Warehousing 0.48
educationMultidisciplinary Studies 0.48
industryAccommodation and Food Services 0.48
industryAdministrative and support and waste management services 0.47
birth_continentEurope 0.47
educationScience and Engineering Related 0.43
educationLiberal Arts and History 0.40
industryOther Services, Except Public Administration 0.40
educationAssociate’s degree 0.40
birth_continentAsia 0.39
educationCommunications 0.38
educationLiterature and Languages 0.35
educationComputers, Mathematics and Statistics 0.34
educationFinance 0.31
educationBusiness 0.28
educationSocial Sciences 0.23
educationOther 0.22
educationEngineering 0.14
educationPsychology 0.10
age 0.07
worked_per_week 0.03
educationPhysical and Related Sciences 0.02
adj_income 0.00
educationBeyond an Associate’s degree 0.00
industryUnemployed in 5 years 0.00

Clustering education based on salary

Hierarchical clustering

# Clustering Education
inc_edu <- main.data %>% 
  select(education, weight, year, adj_income) %>% 
  group_by(education, year) %>% 
  summarise(adj_income = wtd.mean(adj_income, weights = weight, normwt = F)) %>% 
  spread(key = year, value = adj_income)
`summarise()` has grouped output by 'education'. You can override using the `.groups` argument.
kable(inc_edu, digits = 0)
education 2015 2016 2017 2018 2019
Biological Agricultural Environmental Sciences 89532 93370 91670 97804 101434
Visual and Performing Arts 49599 51574 53040 52934 54640
Communications 51884 51096 55485 53853 57793
Computers, Mathematics and Statistics 84839 87093 86923 93529 97149
Other 55058 56115 55878 61056 60385
Education 39345 41014 41452 40974 43230
Engineering 95299 95295 97713 98041 104440
Science and Engineering Related 75156 75448 76049 74263 77561
Literature and Languages 51458 52903 54208 52444 54177
Social Sciences 67583 71353 73326 71695 73961
Liberal Arts and History 55791 57749 56943 59337 62597
Multidisciplinary Studies 73839 68873 75975 74907 75947
Physical and Related Sciences 96969 96372 96128 100339 100478
Psychology 53408 60965 60148 58514 62279
Business 62236 62994 63064 64391 67242
Finance 78744 82340 84172 81222 86944
No diploma 20467 21288 21876 22835 23597
Diploma with no degree 27914 28771 29618 30093 31503
Associate’s degree 37665 38234 38976 38401 40180

In the above table, I weighted average the salaries based on the year and education. Then, I used hierarchical clustering to simplify the model to build a dendrogram of education based on the immigrant’s yearly average salaries. I proposed clusters using a height of 40,000.

temp <- inc_edu$education

row.names(inc_edu) <- inc_edu$education


# Calculate Euclidean distance between the education
dist_inc_edu <- dist(inc_edu, method = "euclidean")

# Generate an average linkage analysis 
hc_inc_edu <- hclust(dist_inc_edu, method = "average")

# Create a dendrogram object from the hclust variable
dend_inc_edu <- as.dendrogram(hc_inc_edu)

# Color branches by cluster formed from the cut at a height of 40000
dend_colored <- color_branches(dend_inc_edu, h = 40000)

# Plot the colored dendrogram
plot(dend_colored) 
title(main = "Education dendrogram",
      ylab = "Average salary")

NA
NA
NA
NA

K-means

To find the best k in the k-means method, I examined two analyses, Elbow analysis and Average Silhouette Widths. K with the highest Average Silhouette Widths should be chosen. From Elbow analysis, the best k is 4, and Average Silhouette Widths suggested the k=5 as the best k. Moreover, hierarchical clustering resulted in 4 clusters. Based on the context of this study, I worked with 4 groups to have a better output.

###### Elbow analysis
inc_edu_trim <- inc_edu %>% 
  ungroup(education) %>% 
  select(-education)

row.names(inc_edu_trim) <- temp

# Use map_dbl to run many models with varying value of k (centers)
tot_withinss <- map_dbl(1:10,  function(k){
  model <- kmeans(x = inc_edu_trim, centers = k)
  model$tot.withinss
})

# Generate a data frame containing both k and tot_withinss
elbow_df <- data.frame(
  k = 1:10,
  tot_withinss = tot_withinss
)

# Plot the elbow plot
ggplot(elbow_df, aes(x = k, y = tot_withinss)) +
  geom_line() +
  scale_x_continuous(breaks = 1:10) +
  labs(title = "Elbow analysis")

###### Average Silhouette Widths
# Use map_dbl to run many models with varying value of k
sil_width <- map_dbl(2:10,  function(k){
  model <- pam(inc_edu_trim, k = k)
  model$silinfo$avg.width
})

# Generate a data frame containing both k and sil_width
sil_df <- data.frame(
  k = 2:10,
  sil_width = sil_width
)

# Plot the relationship between k and sil_width
ggplot(sil_df, aes(x = k, y = sil_width)) +
  geom_line() +
  scale_x_continuous(breaks = 2:10) +
  labs(title = "Average Silhouette Widths")

k_means_edu <- data.frame(method = c("Hierarchical clustering",
                                          "Elbow analysis",
                                          "Average Silhouette Widths"),
                               k = c(4,4,5))
kable(k_means_edu, caption = "Best k?")
Best k?
method k
Hierarchical clustering 4
Elbow analysis 4
Average Silhouette Widths 5

Here are the groups for education based on the average salaries of immigrants between 2015-2019.


# Create a cluster assignment vector at h = 40,000
cut_inc_edu <- cutree(hc_inc_edu, h = 40000)

# Generate the segmented the inc_edu data frame
clust_inc_edu <- inc_edu %>% 
  ungroup(education) %>% 
  mutate(cluster = cut_inc_edu)

# Create a tidy data frame by gathering the year and values into two columns
gathered_inc_edu <- gather(data = clust_inc_edu, 
                       key = year, 
                       value = mean_salary, 
                       -education, -cluster)

# Name each cluster by descending average salary Batch1 to Batch4
gathered_inc_edu$cluster <- factor(gathered_inc_edu$cluster, labels = c("Batch1","Batch3","Batch4","Batch2"))

cluster_edu <- gathered_inc_edu %>% 
  filter(year == 2015) %>% 
  select(education, cluster) %>% 
  arrange(cluster)

kable(cluster_edu)
education cluster
Biological Agricultural Environmental Sciences Batch1
Computers, Mathematics and Statistics Batch1
Engineering Batch1
Physical and Related Sciences Batch1
Visual and Performing Arts Batch3
Communications Batch3
Other Batch3
Literature and Languages Batch3
Liberal Arts and History Batch3
Psychology Batch3
Business Batch3
Education Batch4
No diploma Batch4
Diploma with no degree Batch4
Associate’s degree Batch4
Science and Engineering Related Batch2
Social Sciences Batch2
Multidisciplinary Studies Batch2
Finance Batch2

# Plot the relationship between mean_salary and year and color the lines by the assigned cluster
ggplot(gathered_inc_edu, aes(x = year, y = mean_salary, color = fct_reorder2(cluster, year, mean_salary))) +
    geom_line(aes(group = education)) +
  labs(title = "Clustering the education variable", color = "Salary") +
  ylab("Average Salary") 

Clustering industry based on salary

Hierarchical clustering

I did the exact same steps for the industry variable.

## Clustering Industry
inc_ind <- main.data %>% 
  filter(!is.na(industry), !industry == "Unemployed in 5 years") %>% 
  select(industry, weight, year, adj_income) %>% 
  group_by(industry, year) %>% 
  summarise(adj_income = wtd.mean(adj_income, weights = weight, normwt = F)) %>% 
  spread(key = year, value = adj_income )
`summarise()` has grouped output by 'industry'. You can override using the `.groups` argument.
kable(inc_ind, digits = 0)
industry 2015 2016 2017 2018 2019
Agriculture, Forestry, Fishing, and Hunting 23787 24159 25715 26113 28069
Mining, Quarrying, and Oil and Gas Extraction 98386 92221 90255 93946 96384
Utilities 76261 80125 74908 80390 82749
Construction 37647 39315 41430 42117 43538
Manufacturing 54369 55160 56904 58927 61649
Wholesale Trade 54893 53541 55040 54634 57320
Retail Trade 35098 36710 36636 38055 40132
Transportation and Warehousing 45075 44390 45750 45210 47249
Information 83955 86886 87671 96177 102450
Finance and Insurance 86387 91893 93542 94379 100800
Real Estate and Rental and Leasing 54708 56357 56893 55675 62010
Professional, Scientific, and Technical Services 86406 89982 93404 93153 98532
Management of companies and enterprises 114304 92485 99447 91685 96660
Administrative and support and waste management services 28855 29491 30713 31378 32834
Educational Services 45247 44774 45205 46593 48301
Health Care and Social Assistance 56183 59449 58869 60077 61642
Arts, Entertainment, and Recreation 37802 37383 37750 39353 39713
Accommodation and Food Services 25875 26670 27849 28317 29087
Other Services, Except Public Administration 27417 27963 28735 30056 31806
Public Administration 65277 65255 67908 67826 67018
Military 47930 48925 50709 48607 49649

In the above table, I weighted average the salaries based on the year and industry. Then, I used hierarchical clustering to simplify the model to build a dendrogram of industry based on the immigrant’s yearly average salaries. I proposed clusters using a height of 40,000.

temp <- inc_ind$industry

row.names(inc_ind) <- inc_ind$industry


# Calculate Euclidean distance between the occupations
dist_inc_industry <- dist(inc_ind, method = "euclidean")

# Generate an average linkage analysis 
hc_inc_industry <- hclust(dist_inc_industry, method = "average")

# Create a dendrogram object from the hclust variable
dend_inc_industry <- as.dendrogram(hc_inc_industry)

# Color branches by cluster formed from the cut at a height of 40000
dend_colored <- color_branches(dend_inc_industry, h = 40000)

# Plot the colored dendrogram
plot(dend_colored)
title(main = "Industry dendrogram",
      ylab = "Average salary")

K-means

##### Elbow analysis
inc_ind_trim <- inc_ind %>% 
  ungroup(industry) %>% 
  select(-industry)

row.names(inc_ind_trim) <- temp

# Use map_dbl to run many models with varying value of k (centers)
tot_withinss <- map_dbl(1:10,  function(k){
  model <- kmeans(x = inc_ind_trim, centers = k)
  model$tot.withinss
})

# Generate a data frame containing both k and tot_withinss
elbow_df <- data.frame(
  k = 1:10,
  tot_withinss = tot_withinss
)

# Plot the elbow plot
ggplot(elbow_df, aes(x = k, y = tot_withinss)) +
  geom_line() +
  scale_x_continuous(breaks = 1:10) +
  labs(title = "Elbow analysis")

###### Average Silhouette Widths
# Use map_dbl to run many models with varying value of k
sil_width <- map_dbl(2:10,  function(k){
  model <- pam(inc_ind_trim, k = k)
  model$silinfo$avg.width
})

# Generate a data frame containing both k and sil_width
sil_df <- data.frame(
  k = 2:10,
  sil_width = sil_width
)

# Plot the relationship between k and sil_width
ggplot(sil_df, aes(x = k, y = sil_width)) +
  geom_line() +
  scale_x_continuous(breaks = 2:10) +
  labs(title = "Average Silhouette Widths")

k_means_ind <- data.frame(method = c("Hierarchical clustering",
                                          "Elbow analysis",
                                          "Average Silhouette Widths"),
                               k = c(3,3,2))
kable(k_means_ind, caption = "Best k?")
Best k?
method k
Hierarchical clustering 3
Elbow analysis 3
Average Silhouette Widths 2

Based on the context of this study, I worked with 3 groups to have a better output.

Here are the groups for industry based on the average salaries of immigrants between 2015-2019.


# Create a cluster assignment vector at h = 40,000
cut_inc_industry <- cutree(hc_inc_industry, h = 40000)

# Generate the segmented clust_inc_industry data frame
clust_inc_industry <- inc_ind %>% 
  ungroup(industry) %>% 
  mutate(cluster = cut_inc_industry)

# Create a tidy data frame by gathering the year and values into two columns
gathered_inc_industry <- gather(data = clust_inc_industry, 
                       key = year, 
                       value = mean_salary, 
                       -industry, -cluster)

# Name each cluster by descending average salary Batch1 to Batch3
gathered_inc_industry$cluster <- factor(gathered_inc_industry$cluster, labels = c("Batch3","Batch1","Batch2"))

cluster_ind <- gathered_inc_industry %>% 
  filter(year == 2015) %>% 
  select(industry, cluster) %>% 
  arrange(cluster)

kable(cluster_ind)
industry cluster
Agriculture, Forestry, Fishing, and Hunting Batch3
Construction Batch3
Retail Trade Batch3
Transportation and Warehousing Batch3
Administrative and support and waste management services Batch3
Educational Services Batch3
Arts, Entertainment, and Recreation Batch3
Accommodation and Food Services Batch3
Other Services, Except Public Administration Batch3
Military Batch3
Mining, Quarrying, and Oil and Gas Extraction Batch1
Utilities Batch1
Information Batch1
Finance and Insurance Batch1
Professional, Scientific, and Technical Services Batch1
Management of companies and enterprises Batch1
Manufacturing Batch2
Wholesale Trade Batch2
Real Estate and Rental and Leasing Batch2
Health Care and Social Assistance Batch2
Public Administration Batch2

# Plot the relationship between mean_salary and year and color the lines by the assigned cluster
ggplot(gathered_inc_industry, aes(x = year, y = mean_salary, color = fct_reorder2(cluster, year, mean_salary))) + 
    geom_line(aes(group = industry)) +
  labs(title = "Clustering the industry variable", color = "Salary") +
  ylab("Average Salary") 

Simple model

# preparing data
model.data <- main.data %>% 
  select(weight, birth_continent,  gender, age, education, industry, 
         worked_per_week, adj_income, health_insurance) 
### binding education into 4 clusters
Batch1_edu <- gathered_inc_edu %>% 
  filter(cluster == "Batch1", year == 2015) %>% 
  select(education) %>% 
  pull()

Batch2_edu <- gathered_inc_edu %>% 
  filter(cluster == "Batch2", year == 2015) %>% 
  select(education) %>% 
  pull()

Batch3_edu <- gathered_inc_edu %>% 
  filter(cluster == "Batch3", year == 2015) %>% 
  select(education) %>% 
  pull()

Batch4_edu <- gathered_inc_edu %>% 
  filter(cluster == "Batch4", year == 2015) %>% 
  select(education) %>% 
  pull()


model.data <- model.data %>% 
  mutate(education = fct_collapse(education, 
                                 "Batch1" = as.character(Batch1_edu), 
                                 "Batch2" = as.character(Batch2_edu), 
                                 "Batch3" = as.character(Batch3_edu),
                                 "Batch4" = as.character(Batch4_edu)
                                 ))
### binding industry to 3 clusters
Batch1_ind <- gathered_inc_industry %>% 
  filter(cluster == "Batch1", year == 2015) %>% 
  select(industry) %>% 
  pull()

Batch2_ind <- gathered_inc_industry %>% 
  filter(cluster == "Batch2", year == 2015) %>% 
  select(industry) %>% 
  pull()

Batch3_ind <- gathered_inc_industry %>% 
  filter(cluster == "Batch3", year == 2015) %>% 
  select(industry) %>% 
  pull()


model.data <- model.data %>% 
  mutate(industry = fct_collapse(industry, 
                                 "Batch1" = as.character(Batch1_ind), 
                                 "Batch2" = as.character(Batch2_ind), 
                                 "Batch3" = as.character(Batch3_ind)
                                 )) 

I built the same model of multiple logistic regression. However, I used the cluster analysis from the previous parts to group the education and industry variables. Using that made the number of variables less and calculation more efficient.

Summary and removing with NAs

As seen from the data summary, some observations have missing values. Moreover, I dropped the education and industry level only to consider batches.

# summarizing data
kable(summary(model.data), caption = "Logistic regression variables")
Logistic regression variables
weight birth_continent gender age education industry worked_per_week adj_income health_insurance
Min. : 1.00 North America : 32299 Male :722629 Min. :20.00 Batch1 : 190843 Batch3 :661013 Min. : 1.0 Min. : -10805 No Insurance:281363
1st Qu.: 13.00 South America :717940 Female:782830 1st Qu.:35.00 Batch3 : 192597 Batch1 :180971 1st Qu.:38.0 1st Qu.: 8373 Private :894430
Median : 18.00 Asia :511219 NA Median :45.00 Batch4 :1016763 Batch2 :395220 Median :40.0 Median : 25931 Public :329666
Mean : 24.05 Europe :167965 NA Mean :45.17 Batch2 : 105256 Unemployed in 5 years: 9503 Mean :39.3 Mean : 45710 NA
3rd Qu.: 29.00 Africa : 66454 NA 3rd Qu.:56.00 Beyond an Associate’s degree: 0 NA’s :258752 3rd Qu.:40.0 3rd Qu.: 54840 NA
Max. :435.00 Oceania and at Sea: 9582 NA Max. :69.00 NA NA Max. :99.0 Max. :1788178 NA
NA NA NA NA NA NA NA’s :375611 NA NA

# remove NAs
model.data <- model.data %>% 
  filter(!is.na(worked_per_week), !is.na(industry))

# remove the unused factor levels
model.data$education <- droplevels(model.data$education)
model.data$industry <- droplevels(model.data$industry)

Train and Test dataset

I did the same as the complex model and divided the data into train and test datasets with a 70:30 ratio. The tables below show the proportion of each dataset, indicating that those random samplings have the same ratio for each type of health insurance.

model.data <- model.data %>% 
  mutate(id = row_number())
train.data <- model.data %>% sample_frac(.70)
test.data <-anti_join(model.data, train.data, by='id')

train.simple <- prop.table(wtd.table(train.data$health_insurance, weights = train.data$weight, type = "table"))

kable(train.simple, col.names = "proportion", caption = "Train dataset proportion", format = "markdown")
Train dataset proportion
proportion
No Insurance 0.2187710
Private 0.6330648
Public 0.1481642

test.simple <- prop.table(wtd.table(test.data$health_insurance, weights = test.data$weight, type = "table"))

kable(test.simple, col.names = "proportion", caption = "Test dataset proportion", format = "markdown")
Test dataset proportion
proportion
No Insurance 0.2181572
Private 0.6345566
Public 0.1472862
NA
NA

Build the simple model

time2 <- system.time(multinom.fit <- multinom(health_insurance ~ . -id -weight, maxit=500, data = train.data, weights = weight))
# weights:  48 (30 variable)
initial  value 21104071.806682 
iter  10 value 16236380.328531
iter  20 value 16165926.637845
iter  30 value 16151560.953679
iter  40 value 14764951.650757
final  value 14764951.054872 
converged
p1<-predict(multinom.fit, type="class", newdata=test.data)
eval.model<- postResample(test.data$health_insurance, p1)

This model has 14 different variables, less than one-third of the complex model. The model’s accuracy was 0.6992, only 0.0065 lower than the intricate model. The model efficiency was pretty good; it took 49 seconds to create it. Moreover, the importance of each variable is shown in the table below. Immigrants who have been living in the South American continent have the most impact on predicting the type of health insurance, followed by industries allocated to batch 2.

# The importance of different predictors
imp.model <- varImp(multinom.fit)
imp.model <- imp.model %>% 
  arrange(desc(Overall))
kable(imp.model, digits = 2)
Overall
birth_continentSouth America 2.57
industryBatch2 1.19
industryBatch1 1.10
educationBatch4 1.05
genderFemale 0.96
birth_continentAfrica 0.85
birth_continentOceania and at Sea 0.81
birth_continentEurope 0.66
birth_continentAsia 0.49
educationBatch3 0.36
educationBatch2 0.32
age 0.06
worked_per_week 0.03
adj_income 0.00

Discussion

Here is the summary of my first regression model. The comparison table shows that the nonlinear model gave the best result. I averaged all the immigrants’ salaries based on age and gender in this research. Therefore, this model is not appropriate to predict the new data. It only gives what the average of this community would be if we have the information about the age and gender of an immigrant in the USA. This model is potent to test in other communities since this data has a large sample size and can be generalized.

`Adjusted R-squared` = c(round(summary.age$adj.r.squared*100,2)/100,
                         round(summary.gender$adj.r.squared*100,2)/100,
                         round(summary.interact$adj.r.squared*100,2)/100,
                         round(poly.model.summary$adj.r.squared*100,2)/100)

AIC = c(AIC1, AIC2, AIC3, AIC4)

Issues = c("Cond. Het, Non-normality, Very Low Adjusted R-squared", 
           "Cond. Het.", 
           "Cond. Het., Lower Adjusted R-squared", 
           "-")
compare.regression <- data.frame(AIC, `Adjusted R-squared`, Issues)

row.names(compare.regression) <- c("only age", "age & gender", "interaction", "non-linear")

kable(compare.regression, caption = "Comparing regression models of explaining salary", align = "c")
Comparing regression models of explaining salary
AIC Adjusted.R.squared Issues
only age 2224.648 0.1266 Cond. Het, Non-normality, Very Low Adjusted R-squared
age & gender 2140.550 0.9434 Cond. Het.
interaction 2134.640 0.6517 Cond. Het., Lower Adjusted R-squared
non-linear 1750.272 0.9927 -

The efficiency and accuracy of multiple logistic regression models are compared below. As it can be seen, the simple model is way better than the complex one. Generally, accuracy between 0.62-0.75 is pretty good for multiple logistic regression models. Therefore, I am confident in these findings, and this result is helpful for health insurance companies to target which people can be marketed for either public or private health insurance. This study can be further investigated using other machine learning techniques such as random forest and decision trees to see which model gives us better results.

Accuracy = c(round(as.numeric(eval.big.model[1])*100,2)/100,
             round(as.numeric(eval.model[1])*100,2)/100)

AIC.logistic = c(multinom.fit1$AIC, multinom.fit$AIC)

time.logistic = c(round(as.numeric(time1[1])), round(as.numeric(time2[1])))

`No of variables` = c(multinom.fit1$n[1]-1, multinom.fit$n[1]-1)
compare.logistic.regression <- data.frame(Accuracy, AIC.logistic, time.logistic, `No of variables`)

row.names(compare.logistic.regression) <- c("Complex model", "Simple model")

colnames(compare.logistic.regression) <- c("Accuracy", "AIC", "Time to solve", "No of variables")
kable(compare.logistic.regression, caption = "Comparing logistic regression models of predicting health insurance type", align = "c")
Comparing logistic regression models of predicting health insurance type
Accuracy AIC Time to solve No of variables
Complex model 0.7057 28732237 337 49
Simple model 0.6992 29529962 49 14

My study has limitations, such as top-coded variable adj_income or filtering the age between 20 and 70. Also, the data is sampled and weighted by some statistical tools to demonstrate the whole population, which might be incorrect and influence the result of this study.

LS0tCnRpdGxlOiAnRmluYWwgUHJvamVjdCAtIEFNT0QgNTI1MEgnCmF1dGhvcjogIkFsaSBUYWhzaWxpIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6ICc1JwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNQogICAgdGhlbWU6IHBhcGVyCiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICBzZWxmX2NvbnRhaW5lZDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UpCiNQYWNrYWdlIG1hbmFnZW1lbnQgKHlvdSBkb24ndCBuZWVkIHRvIGRvIGFueXRoaW5nIHdpdGggdGhpcywgaXQganVzdCBtYWtlcyBzdXJlIHlvdSBoYXZlIGFsbCB0aGUgI3BhY2thZ2VzIHlvdSBuZWVkIGZvciB0aGlzIGFzc2lnbm1lbnQpOgoKIyBjaGVjayBpZiBwYWNtYW4gaXMgaW5zdGFsbCwgaWYgbm90LCBpbnN0YWxsIGl0IGZyb20gY3JhbiBhbmQgbG9hZAoKaWYgKCFyZXF1aXJlKCJwYWNtYW4iKSkgewogICAgICBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iLCBkZXBlbmRlbmNpZXM9VFJVRSwgcmVwb3M9J2h0dHA6Ly9jcmFuLnJzdHVkaW8uY29tLycpCiAgICAgIGxpYnJhcnkocGFjbWFuKQp9CgojIHBhY2thZ2VzIHVzZWQgaW4gdGhpcyByZXBvcnQ6CnBhY21hbjo6cF9sb2FkKCJ0aWR5dmVyc2UiLAogICAgICAgICAgICAgICAiZGF0YS50YWJsZSIsCiAgICAgICAgICAgICAgICJ1c21hcCIsCiAgICAgICAgICAgICAgICJIbWlzYyIsCiAgICAgICAgICAgICAgICJjbHVzdGVyIiwKICAgICAgICAgICAgICAgImRlbmRleHRlbmQiLAogICAgICAgICAgICAgICAid2VpZ2h0cyIsCiAgICAgICAgICAgICAgICJjYXJldCIsCiAgICAgICAgICAgICAgICJmb3JtYXR0YWJsZSIsCiAgICAgICAgICAgICAgICJubmV0IiwKICAgICAgICAgICAgICAgImtuaXRyIiwKICAgICAgICAgICAgICAgIkdHYWxseSIsCiAgICAgICAgICAgICAgICJjb3JycGxvdCIsCiAgICAgICAgICAgICAgICJzdHJpbmdyIiwgCiAgICAgICAgICAgICAgICJyc3RhdGl4IiwgCiAgICAgICAgICAgICAgICJ0aWR5dGV4dCIsCiAgICAgICAgICAgICAgICJ0ZXh0ZGF0YSIsCiAgICAgICAgICAgICAgICJ3b3JkY2xvdWQiLAogICAgICAgICAgICAgICAicmVzaGFwZTIiLAogICAgICAgICAgICAgICAiaWdyYXBoIiwKICAgICAgICAgICAgICAgImdsdWUiKQpgYGAKCmBgYHtjc3MsIGVjaG89RkFMU0V9Ci5hbnMgewogICBiYWNrZ3JvdW5kLWNvbG9yOnJnYigyNDgsMjQ4LDI0OCk7IAogIHBhZGRpbmc6IDFlbTsgCiAgYm9yZGVyOiAuMWVtIHNvbGlkICNDQ0M7CiAgYm9yZGVyLXJhZGl1czouMmVtOwogIGNvbG9yOnB1cnBsZTsKICBtYXJnaW46IDFlbSAwOwp9CmBgYAoKCgoKYGBge3IsIGV2YWw9RkFMU0V9CiMjIGRhdGEgZ2F0aGVyaW5nClBhcnRBIDwtIGZyZWFkKCJwc2FtX3B1c2EuY3N2IikKRGF0YTEgPC0gUGFydEEgJT4lIAogIGZpbHRlcihDSVQgJWluJSBjKDQsNSksIEFHRVAgPj0gMjAsIEFHRVAgPCA3MCkgJT4lIAogIHNlbGVjdChQV0dUUCwgUE9CUCwgV0FPQiwgU0VYLCBBR0VQLCBTVCwgTUFSLCBTQ0hMLCBGT0QxUCwgRk9EMlAsIElORFAsIAogICAgICAgICBXS0hQLCBQSU5DUCwgQURKSU5DLCBQT1ZQSVAsIFlPRVAsIEhJQ09WLCBQUklWQ09WLCBQVUJDT1YpCnJtKFBhcnRBKQoKUGFydEIgPC0gZnJlYWQoZmlsZSA9ICJwc2FtX3B1c2IuY3N2IikKRGF0YTIgPC0gUGFydEIgJT4lIAogIGZpbHRlcihDSVQgJWluJSBjKDQsNSksIEFHRVAgPj0gMjAsIEFHRVAgPCA3MCkgJT4lIAogIHNlbGVjdChQV0dUUCwgUE9CUCwgV0FPQiwgU0VYLCBBR0VQLCBTVCwgTUFSLCBTQ0hMLCBGT0QxUCwgRk9EMlAsIElORFAsIAogICAgICAgICBXS0hQLCBQSU5DUCwgQURKSU5DLCBQT1ZQSVAsIFlPRVAsIEhJQ09WLCBQUklWQ09WLCBQVUJDT1YpCnJtKFBhcnRCKQoKUGFydEMgPC0gZnJlYWQoZmlsZSA9ICJwc2FtX3B1c2MuY3N2IikKRGF0YTMgPC0gUGFydEMgJT4lIAogIGZpbHRlcihDSVQgJWluJSBjKDQsNSksIEFHRVAgPj0gMjAsIEFHRVAgPCA3MCkgJT4lIAogIHNlbGVjdChQV0dUUCwgUE9CUCwgV0FPQiwgU0VYLCBBR0VQLCBTVCwgTUFSLCBTQ0hMLCBGT0QxUCwgRk9EMlAsIElORFAsIAogICAgICAgICBXS0hQLCBQSU5DUCwgQURKSU5DLCBQT1ZQSVAsIFlPRVAsIEhJQ09WLCBQUklWQ09WLCBQVUJDT1YpCnJtKFBhcnRDKQoKUGFydEQgPC0gZnJlYWQoZmlsZSA9ICJwc2FtX3B1c2QuY3N2IikKRGF0YTQgPC0gUGFydEQgJT4lIAogIGZpbHRlcihDSVQgJWluJSBjKDQsNSksIEFHRVAgPj0gMjAsIEFHRVAgPCA3MCkgJT4lIAogIHNlbGVjdChQV0dUUCwgUE9CUCwgV0FPQiwgU0VYLCBBR0VQLCBTVCwgTUFSLCBTQ0hMLCBGT0QxUCwgRk9EMlAsIElORFAsIAogICAgICAgICBXS0hQLCBQSU5DUCwgQURKSU5DLCBQT1ZQSVAsIFlPRVAsIEhJQ09WLCBQUklWQ09WLCBQVUJDT1YpCnJtKFBhcnREKQoKbWFpbi5kYXRhIDwtIGJpbmRfcm93cyhEYXRhMSwgRGF0YTIsIERhdGEzLCBEYXRhNCkKcm0oRGF0YTEsRGF0YTIsRGF0YTMsRGF0YTQpCgptYWluLmRhdGEgPC0gYXNfdGliYmxlKG1haW4uZGF0YSkKCmBgYAoKCgoKCgpgYGB7ciwgZXZhbD1GQUxTRX0KIyMjIENyZWF0aW5nIDIgZGF0YXNldHMgZnJvbSBQVU1TIERpY3Rpb25hcnkKCnB1bXNfZGljIDwtIHJlYWRfY3N2KCJDb3B5T2ZQVU1TX0RhdGFfRGljdGlvbmFyeV8yMDE1LTIwMTkuY3N2IikKClBPQlAgPC0gcHVtc19kaWMgJT4lIAogIGZpbHRlcihQVU1TX1ZhcmlhYmxlX05hbWUgPT0gIlBPQlAiKSAlPiUgCiAgc2VsZWN0KFBPQiA9IFN0YXJ0aW5nX0xlZ2FsX1ZhbHVlLCBEZXNjcmlwdGlvbikgJT4lIAogIHNsaWNlKC0xKQoKU1QgPC0gcHVtc19kaWMgJT4lIAogIGZpbHRlcihQVU1TX1ZhcmlhYmxlX05hbWUgPT0gIlNUIikgJT4lIAogIHNlbGVjdChTVCA9IFN0YXJ0aW5nX0xlZ2FsX1ZhbHVlLCBEZXNjcmlwdGlvbikgJT4lIAogIHNsaWNlKDI6NTIpICU+JSAKICBzZXBhcmF0ZShEZXNjcmlwdGlvbiwgaW50byA9IGMoInN0YXRlX25hbWUiLCAic3RhdGVfYWJiIiksIHNlcCA9ICIvIikKCmBgYAoKYGBge3IsIGV2YWw9RkFMU0V9CiMjIGZhY3Rvcml6aW5nIHRoZSB2YXJpYWJsZXMgcHJvcGVybHkKY29sbmFtZXMobWFpbi5kYXRhKSA8LSBjKCJ3ZWlnaHQiLCAiYmlydGhfcGxhY2UiLCAiYmlydGhfY29udGluZW50IiwgImdlbmRlciIsIAogICAgICAgICAgICAgICAgICAgICAgICAgImFnZSIsICJyZXNpZGVudF9zdGF0ZSIsICJtYXJpdGFsX3N0YXR1cyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgImVkdWNhdGlvbmFsX2F0dGFpbm1lbnQiLCAiZmlyc3RfZGVncmVlIiwgInNlY29uZF9kZWdyZWUiLCAgCiAgICAgICAgICAgICAgICAgICAgICAgICAiaW5kdXN0cnkiLCAid29ya2VkX3Blcl93ZWVrIiwgInRvdGFsX2luY29tZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgImFkanVzdF9mYWN0b3IiLCAiSW5jb21lX3RvX3BvdmVydHlfcmF0aW8iLCAieWVhcl9lbnRyeSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgImhlYWx0aF9pbnN1cmFuY2UiLCAicHJpdmF0ZV9oZWFsdGhfaW5zdXJhbmNlX2JpbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAicHVibGljX2hlYWx0aF9jb3ZlcmFnZV9iaW4iKQpyZW5hbWVkLnRvIDwtIGNvbG5hbWVzKG1haW4uZGF0YSkKCm1haW4uZGF0YSA8LSBtYWluLmRhdGEgJT4lIG11dGF0ZSh5ZWFyID0gY2FzZV93aGVuKAogIGFkanVzdF9mYWN0b3IgPT0gMTA4MDQ3MCB+IDIwMTUsCiAgYWRqdXN0X2ZhY3RvciA9PSAxMDczNDQ5IH4gMjAxNiwKICBhZGp1c3RfZmFjdG9yID09IDEwNTQ2MDYgfiAyMDE3LAogIGFkanVzdF9mYWN0b3IgPT0gMTAzMTQ1MiB+IDIwMTgsCiAgVFJVRSB+IDIwMTkKKSkgJT4lIAogIHNlbGVjdCh5ZWFyLCBldmVyeXRoaW5nKCkpICU+JSAKICBtdXRhdGUoYWRqX2luY29tZSA9IHJvdW5kKHRvdGFsX2luY29tZSooYWRqdXN0X2ZhY3Rvci8xMDAwMDAwKSkpICU+JSAKICBzZWxlY3QoeWVhcjp3b3JrZWRfcGVyX3dlZWssIGFkal9pbmNvbWUsIEluY29tZV90b19wb3ZlcnR5X3JhdGlvOnB1YmxpY19oZWFsdGhfY292ZXJhZ2VfYmluLCAtYWRqdXN0X2ZhY3RvciwgLXRvdGFsX2luY29tZSkKCgojIFBsYWNlIG9mIGJpcnRoClBPQl9hZGo8LSBQT0JQICU+JSAKICBmaWx0ZXIoIFBPQiAlaW4lIHVuaXF1ZShtYWluLmRhdGEkYmlydGhfcGxhY2UpKQptYWluLmRhdGEkYmlydGhfcGxhY2UgPC0gZmFjdG9yKG1haW4uZGF0YSRiaXJ0aF9wbGFjZSwgbGFiZWxzID0gUE9CX2FkaiREZXNjcmlwdGlvbikKCiMgV29ybGQgYXJlYSBwbGFjZSBvZiBiaXJ0aAptYWluLmRhdGEkYmlydGhfY29udGluZW50IDwtIGZhY3RvcihtYWluLmRhdGEkYmlydGhfY29udGluZW50ICwgbGFiZWxzID0gYygiUFIgYW5kIFVTIElzbGFuZCBBcmVhcyIsIkxhdGluIEFtZXJpY2EiLCAiQXNpYSIsICJFdXJvcGUiLCAiQWZyaWNhIiwgIk5vcnRoZXJuIEFtZXJpY2EiLCAiT2NlYW5pYSBhbmQgYXQgU2VhIikpCgptYWluLmRhdGEgPC0gbWFpbi5kYXRhICU+JSBtdXRhdGUoYmlydGhfY29udGluZW50ID0gZmN0X3JlY29kZShiaXJ0aF9jb250aW5lbnQsCiAgICAiTm9ydGggQW1lcmljYSIgICAgPSAiUFIgYW5kIFVTIElzbGFuZCBBcmVhcyIsCiAgICAiU291dGggQW1lcmljYSIgICAgICA9ICJMYXRpbiBBbWVyaWNhIiwKICAgICJOb3J0aCBBbWVyaWNhIiA9ICJOb3J0aGVybiBBbWVyaWNhIiwKICAgICJPY2VhbmlhIGFuZCBhdCBTZWEiID0gIk9jZWFuaWEgYW5kIGF0IFNlYSIKICApKQoKIyBHZW5kZXIKbWFpbi5kYXRhJGdlbmRlciA8LSBmYWN0b3IobWFpbi5kYXRhJGdlbmRlciAsIGxhYmVscyA9IGMoIk1hbGUiLCJGZW1hbGUiKSkKCiMgU3RhdGUKbWFpbi5kYXRhJHJlc2lkZW50X3N0YXRlIDwtIGZhY3RvcihtYWluLmRhdGEkcmVzaWRlbnRfc3RhdGUsbGFiZWxzID0gU1Qkc3RhdGVfYWJiKQoKIyBNYXJpdGFsIHN0YXR1cyAKbWFpbi5kYXRhJG1hcml0YWxfc3RhdHVzIDwtIGZhY3RvcihtYWluLmRhdGEkbWFyaXRhbF9zdGF0dXMgLCBsYWJlbHMgPSBjKCJNYXJyaWVkIiwiV2lkb3dlZCIsICJEaXZvcmNlZCIsICJTZXBhcmF0ZWQiLCAiTmV2ZXIgbWFycmllZCBvciB1bmRlciAxNSB5ZWFycyBvbGQiKSkKCiMgRWR1Y2F0aW9uYWwgYXR0YWlubWVudAptYWluLmRhdGEkZWR1Y2F0aW9uYWxfYXR0YWlubWVudCA8LSBmYWN0b3IobWFpbi5kYXRhJGVkdWNhdGlvbmFsX2F0dGFpbm1lbnQpCgptYWluLmRhdGEgPC0gbWFpbi5kYXRhICU+JQogIG11dGF0ZShlZHVjYXRpb25hbF9hdHRhaW5tZW50ID0gZmN0X2NvbGxhcHNlKGVkdWNhdGlvbmFsX2F0dGFpbm1lbnQsCiAgICAiTm8gZGlwbG9tYSIgPSBhcy5jaGFyYWN0ZXIocmVwKDAxOjE1KSksCiAgICAiRGlwbG9tYSB3aXRoIG5vIGRlZ3JlZSIgPSBhcy5jaGFyYWN0ZXIocmVwKDE2OjE5KSksCiAgICAiQXNzb2NpYXRlJ3MgZGVncmVlIiA9IGFzLmNoYXJhY3RlcigyMCksCiAgICAiQmV5b25kIGFuIEFzc29jaWF0ZSdzIGRlZ3JlZSIgPSBhcy5jaGFyYWN0ZXIocmVwKDIxOjI0KSkKICAgICkpCgojIEZpcnN0IGRlZ3JlZQptYWluLmRhdGEkZmlyc3RfZGVncmVlIDwtIGFzLmZhY3RvcihtYWluLmRhdGEkZmlyc3RfZGVncmVlKQoKbWFpbi5kYXRhIDwtIG1haW4uZGF0YSAlPiUKICBtdXRhdGUoZmlyc3RfZGVncmVlID0gZmN0X2NvbGxhcHNlKGZpcnN0X2RlZ3JlZSwKICAgIGBDb21wdXRlcnMsIE1hdGhlbWF0aWNzIGFuZCBTdGF0aXN0aWNzYCA9IGFzLmNoYXJhY3RlcihjKHJlcCgyMDAxOjIxMDcpLHJlcCgzNzAwOjM3MDIpLDQwMDUpKSwKICAgIGBCaW9sb2dpY2FsIEFncmljdWx0dXJhbCBFbnZpcm9ubWVudGFsIFNjaWVuY2VzYCA9IGFzLmNoYXJhY3RlcihjKHJlcCgxMTAwOjEzMDMpLHJlcCgzNjAwOjM2OTkpKSksCiAgICBgUGh5c2ljYWwgYW5kIFJlbGF0ZWQgU2NpZW5jZXNgID0gYXMuY2hhcmFjdGVyKHJlcCg1MDAwOjUwMDkpKSwKICAgIFBzeWNob2xvZ3kgPSBhcy5jaGFyYWN0ZXIocmVwKDUyMDA6NTI5OSkpLAogICAgYFNvY2lhbCBTY2llbmNlc2AgPSBhcy5jaGFyYWN0ZXIoYygyOTAxLDUzMDEscmVwKDU1MDA6NTU5OSkpKSwKICAgIEVuZ2luZWVyaW5nID0gYXMuY2hhcmFjdGVyKHJlcCgyNDAwOjI1MDIpKSwKICAgIGBNdWx0aWRpc2NpcGxpbmFyeSBTdHVkaWVzYCA9IGFzLmNoYXJhY3RlcihjKHJlcCg0MDAwOjQwMDIpLDQwMDYsNDAwNyw1MDk4KSksCiAgICBgU2NpZW5jZSBhbmQgRW5naW5lZXJpbmcgUmVsYXRlZGAgPSBhcy5jaGFyYWN0ZXIoYyhyZXAoMjUwMzoyNTk5KSw1NzAxLHJlcCg2MTAwOjYxOTkpKSksCiAgICBCdXNpbmVzcyA9IGFzLmNoYXJhY3RlcihjKHJlcCg2MjAwOjYyMDYpLHJlcCg2MjA5OjYyOTkpKSksCiAgICBGaW5hbmNlID0gYXMuY2hhcmFjdGVyKDYyMDcpLAogICAgRWR1Y2F0aW9uID0gYXMuY2hhcmFjdGVyKHJlcCgyMzAwOjIzOTkpKSwKICAgIGBMaXRlcmF0dXJlIGFuZCBMYW5ndWFnZXNgID0gYXMuY2hhcmFjdGVyKGMocmVwKDI2MDE6MjYwMyksMzMwMSwzMzAyKSksCiAgICBgTGliZXJhbCBBcnRzIGFuZCBIaXN0b3J5YCA9IGFzLmNoYXJhY3RlcihjKHJlcCgzNDAxOjM1MDEpLDQ4MDEsNDkwMSw2NDAyLDY0MDMpKSwKICAgIGBWaXN1YWwgYW5kIFBlcmZvcm1pbmcgQXJ0c2AgPSBhcy5jaGFyYWN0ZXIoYygxNDAxLDE1MDEscmVwKDYwMDA6NjA5OSksNjAwMikpLAogICAgQ29tbXVuaWNhdGlvbnMgPSBhcy5jaGFyYWN0ZXIocmVwKDE5MDE6MTkwNCkpLAogICAgT3RoZXIgPSBhcy5jaGFyYWN0ZXIoYygyMjAxLDMyMDEsMzIwMiwzODAxLDQxMDEsNTEwMixyZXAoNTQwMTo1NDA0KSw1NjAxLDU5MDEpKSkpCgojIyMgUmVwbGFjZSBOQSB2YWx1ZSBpbiBmaXJzdF9kZWdyZWUgYW5kIHJlcGxhY2UgaXQgd2l0aCBlZHVjYXRpb25hbF9hdHRhaW5tZW50Cm1haW4uZGF0YSA8LSBtYWluLmRhdGEgJT4lIAogICAgbXV0YXRlKGZpcnN0X2RlZ3JlZSA9IGNvYWxlc2NlKGZpcnN0X2RlZ3JlZSwgZWR1Y2F0aW9uYWxfYXR0YWlubWVudCkpICU+JSAKICBzZWxlY3QoLWVkdWNhdGlvbmFsX2F0dGFpbm1lbnQpICU+JSAKICByZW5hbWUoZWR1Y2F0aW9uID0gZmlyc3RfZGVncmVlKQoKIyBTZWNvbmQgZGVncmVlCm1haW4uZGF0YSRzZWNvbmRfZGVncmVlIDwtIGFzLmZhY3RvcihtYWluLmRhdGEkc2Vjb25kX2RlZ3JlZSkKCm1haW4uZGF0YSA8LSBtYWluLmRhdGEgJT4lCiAgbXV0YXRlKHNlY29uZF9kZWdyZWUgPSBmY3RfY29sbGFwc2Uoc2Vjb25kX2RlZ3JlZSwKICAgIGBDb21wdXRlcnMsIE1hdGhlbWF0aWNzIGFuZCBTdGF0aXN0aWNzYCA9IGFzLmNoYXJhY3RlcihjKHJlcCgyMDAxOjIxMDcpLHJlcCgzNzAwOjM3MDIpLDQwMDUpKSwKICAgIGBCaW9sb2dpY2FsIEFncmljdWx0dXJhbCBFbnZpcm9ubWVudGFsIFNjaWVuY2VzYCA9IGFzLmNoYXJhY3RlcihjKHJlcCgxMTAwOjEzMDMpLHJlcCgzNjAwOjM2OTkpKSksCiAgICBgUGh5c2ljYWwgYW5kIFJlbGF0ZWQgU2NpZW5jZXNgID0gYXMuY2hhcmFjdGVyKHJlcCg1MDAwOjUwMDkpKSwKICAgIFBzeWNob2xvZ3kgPSBhcy5jaGFyYWN0ZXIocmVwKDUyMDA6NTI5OSkpLAogICAgYFNvY2lhbCBTY2llbmNlc2AgPSBhcy5jaGFyYWN0ZXIoYygyOTAxLDUzMDEscmVwKDU1MDA6NTU5OSkpKSwKICAgIEVuZ2luZWVyaW5nID0gYXMuY2hhcmFjdGVyKHJlcCgyNDAwOjI1MDIpKSwKICAgIGBNdWx0aWRpc2NpcGxpbmFyeSBTdHVkaWVzYCA9IGFzLmNoYXJhY3RlcihjKHJlcCg0MDAwOjQwMDIpLDQwMDYsNDAwNyw1MDk4KSksCiAgICBgU2NpZW5jZSBhbmQgRW5naW5lZXJpbmcgUmVsYXRlZGAgPSBhcy5jaGFyYWN0ZXIoYyhyZXAoMjUwMzoyNTk5KSw1NzAxLHJlcCg2MTAwOjYxOTkpKSksCiAgICBCdXNpbmVzcyA9IGFzLmNoYXJhY3RlcihjKHJlcCg2MjAwOjYyMDYpLHJlcCg2MjA5OjYyOTkpKSksCiAgICBGaW5hbmNlID0gYXMuY2hhcmFjdGVyKDYyMDcpLAogICAgRWR1Y2F0aW9uID0gYXMuY2hhcmFjdGVyKHJlcCgyMzAwOjIzOTkpKSwKICAgIGBMaXRlcmF0dXJlIGFuZCBMYW5ndWFnZXNgID0gYXMuY2hhcmFjdGVyKGMocmVwKDI2MDE6MjYwMyksMzMwMSwzMzAyKSksCiAgICBgTGliZXJhbCBBcnRzIGFuZCBIaXN0b3J5YCA9IGFzLmNoYXJhY3RlcihjKHJlcCgzNDAxOjM1MDEpLDQ4MDEsNDkwMSw2NDAyLDY0MDMpKSwKICAgIGBWaXN1YWwgYW5kIFBlcmZvcm1pbmcgQXJ0c2AgPSBhcy5jaGFyYWN0ZXIoYygxNDAxLDE1MDEscmVwKDYwMDA6NjA5OSksNjAwMikpLAogICAgQ29tbXVuaWNhdGlvbnMgPSBhcy5jaGFyYWN0ZXIocmVwKDE5MDE6MTkwNCkpLAogICAgT3RoZXIgPSBhcy5jaGFyYWN0ZXIoYygyMjAxLDMyMDEsMzIwMiwzODAxLDQxMDEsNTEwMixyZXAoNTQwMTo1NDA0KSw1NjAxLDU5MDEpKSkpCgojIEluZHVzdHJ5Cm1haW4uZGF0YSRpbmR1c3RyeSA8LSBhcy5mYWN0b3IobWFpbi5kYXRhJGluZHVzdHJ5KQoKbWFpbi5kYXRhIDwtIG1haW4uZGF0YSAlPiUKICBtdXRhdGUoaW5kdXN0cnkgPSBmY3RfY29sbGFwc2UoaW5kdXN0cnksCiAgICAiQWdyaWN1bHR1cmUsIEZvcmVzdHJ5LCBGaXNoaW5nLCBhbmQgSHVudGluZyIgPSBhcy5jaGFyYWN0ZXIocmVwKDAxNzA6MDI5MCkpLAogICAgIk1pbmluZywgUXVhcnJ5aW5nLCBhbmQgT2lsIGFuZCBHYXMgRXh0cmFjdGlvbiIgPSBhcy5jaGFyYWN0ZXIocmVwKDAzNzA6MDQ5MCkpLAogICAgVXRpbGl0aWVzID0gYXMuY2hhcmFjdGVyKHJlcCgwNTcwOjA2OTApKSwKICAgIENvbnN0cnVjdGlvbiA9IGFzLmNoYXJhY3RlcigwNzcwKSwKICAgIE1hbnVmYWN0dXJpbmcgPSBhcy5jaGFyYWN0ZXIocmVwKDEwNzA6Mzk5MCkpLAogICAgIldob2xlc2FsZSBUcmFkZSI9IGFzLmNoYXJhY3RlcihyZXAoNDA3MDo0NTkwKSksCiAgICAiUmV0YWlsIFRyYWRlIiA9IGFzLmNoYXJhY3RlcihyZXAoNDY3MDo1NzkwKSksCiAgICAiVHJhbnNwb3J0YXRpb24gYW5kIFdhcmVob3VzaW5nIiA9IGFzLmNoYXJhY3RlcihyZXAoNjA3MDo2MzkwKSksCiAgICAiSW5mb3JtYXRpb24iID0gYXMuY2hhcmFjdGVyKHJlcCg2NDcwOjY3ODApKSwKICAgICJGaW5hbmNlIGFuZCBJbnN1cmFuY2UiID0gYXMuY2hhcmFjdGVyKHJlcCg2ODcwOjY5OTIpKSwKICAgICJSZWFsIEVzdGF0ZSBhbmQgUmVudGFsIGFuZCBMZWFzaW5nIiA9IGFzLmNoYXJhY3RlcihyZXAoNzA3MTo3MTkwKSksCiAgICAiUHJvZmVzc2lvbmFsLCBTY2llbnRpZmljLCBhbmQgVGVjaG5pY2FsIFNlcnZpY2VzIiA9IGFzLmNoYXJhY3RlcihyZXAoNzI3MDo3NDkwKSksCiAgICAiTWFuYWdlbWVudCBvZiBjb21wYW5pZXMgYW5kIGVudGVycHJpc2VzIiA9IGFzLmNoYXJhY3Rlcig3NTcwKSwKICAgICJBZG1pbmlzdHJhdGl2ZSBhbmQgc3VwcG9ydCBhbmQgd2FzdGUgbWFuYWdlbWVudCBzZXJ2aWNlcyIgPSBhcy5jaGFyYWN0ZXIocmVwKDc1ODA6Nzc5MCkpLAogICAgIkVkdWNhdGlvbmFsIFNlcnZpY2VzIiA9IGFzLmNoYXJhY3RlcihyZXAoNzg2MDo3ODkwKSksCiAgICAiSGVhbHRoIENhcmUgYW5kIFNvY2lhbCBBc3Npc3RhbmNlIiA9IGFzLmNoYXJhY3RlcihyZXAoNzk3MDo4NDcwKSksCiAgICAiQXJ0cywgRW50ZXJ0YWlubWVudCwgYW5kIFJlY3JlYXRpb24iID0gYXMuY2hhcmFjdGVyKHJlcCg4NTYxOjg1OTApKSwKICAgICJBY2NvbW1vZGF0aW9uIGFuZCBGb29kIFNlcnZpY2VzIiA9IGFzLmNoYXJhY3RlcihyZXAoODY2MDo4NjkwKSksCiAgICAiT3RoZXIgU2VydmljZXMsIEV4Y2VwdCBQdWJsaWMgQWRtaW5pc3RyYXRpb24iID0gYXMuY2hhcmFjdGVyKHJlcCg4NzcwOjkyOTApKSwKICAgICJQdWJsaWMgQWRtaW5pc3RyYXRpb24iID0gYXMuY2hhcmFjdGVyKHJlcCg5MzcwOjk1OTApKSwKICAgICJNaWxpdGFyeSIgPSBhcy5jaGFyYWN0ZXIocmVwKDk2NzA6OTg3MCkpLAogICAgIlVuZW1wbG95ZWQgaW4gNSB5ZWFycyIgPSBhcy5jaGFyYWN0ZXIoOTkyMCkKICAgICkpCgojIEhlYWx0aCBpbnN1cmFuY2UgY292ZXJhZ2U/Cm1haW4uZGF0YSRoZWFsdGhfaW5zdXJhbmNlIDwtIGZhY3RvcihtYWluLmRhdGEkaGVhbHRoX2luc3VyYW5jZSwgbGFiZWxzID0gYygiWWVzIiwiTm8iKSkKCiMgUHJpdmF0ZSBoZWFsdGggaW5zdXJhbmNlIGNvdmVyYWdlPwptYWluLmRhdGEkcHJpdmF0ZV9oZWFsdGhfaW5zdXJhbmNlX2JpbiA8LSBmYWN0b3IobWFpbi5kYXRhJHByaXZhdGVfaGVhbHRoX2luc3VyYW5jZV9iaW4sIGxhYmVscyA9IGMoIlllcyIsIk5vIikpCgojIFB1YmxpYyBoZWFsdGggY292ZXJhZ2U/Cm1haW4uZGF0YSRwdWJsaWNfaGVhbHRoX2NvdmVyYWdlX2JpbiA8LSBmYWN0b3IobWFpbi5kYXRhJHB1YmxpY19oZWFsdGhfY292ZXJhZ2VfYmluLCBsYWJlbHMgPSBjKCJZZXMiLCJObyIpKQoKIyBDb252ZXJ0IEhlYWx0aCBpbnN1cmFuY2UgdG8gdGhyZWUgdmFsdWVzCm1haW4uZGF0YSA8LSBtYWluLmRhdGEgJT4lIAogIG11dGF0ZShoZWFsdGhfaW5zdXJhbmNlID0gY2FzZV93aGVuKAogICAgcHVibGljX2hlYWx0aF9jb3ZlcmFnZV9iaW4gPT0gIlllcyIgfiAiUHVibGljIiwKICAgIHByaXZhdGVfaGVhbHRoX2luc3VyYW5jZV9iaW4gPT0gIlllcyIgfiAiUHJpdmF0ZSIsCiAgICBUUlVFIH4gIk5vIEluc3VyYW5jZSIKKSkgJT4lIHNlbGVjdCgtcHJpdmF0ZV9oZWFsdGhfaW5zdXJhbmNlX2JpbiwgLXB1YmxpY19oZWFsdGhfY292ZXJhZ2VfYmluKQoKbWFpbi5kYXRhJGhlYWx0aF9pbnN1cmFuY2UgPC0gYXMuZmFjdG9yKG1haW4uZGF0YSRoZWFsdGhfaW5zdXJhbmNlKQoKc2F2ZShtYWluLmRhdGEgLGZpbGUgPSAidGlkeS5kYXRhLlJEYXRhIikKYGBgCgojIERhdGEgc3VtbWFyeToKYGBge3J9CiMgTG9hZCB0aGUgZGF0YQpsb2FkKCJ0aWR5LmRhdGEuUkRhdGEiKQpgYGAKCjxkaXY+ClRoZSBkYXRhIEkgYW0gd29ya2luZyBvbiBpcyBmcm9tIHRoZSAyMDE5IEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXkgKEFDUykgNS15ZWFyIFB1YmxpYyBVc2UgTWljcm9kYXRhIFNhbXBsZXMgKFBVTVMpLCBhIHNhbXBsZSBvZiB0aGUgYWN0dWFsIHJlc3BvbnNlcyBjb2xsZWN0ZWQgYnkgdGhlIEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXkgYmV0d2VlbiAyMDE1LTIwMTkgc3BsaXQgaW50byB0aGUgcG9wdWxhdGlvbiBhbmQgaG91c2Vob2xkIGNoYXJhY3RlcmlzdGljcy4gSSBoYXZlIHNlbGVjdGVkIHBvcHVsYXRpb24gZGF0YSwgYW5kIGVhY2ggcmVjb3JkIHJlcHJlc2VudHMgYSBwb3B1bGF0aW9uIHNhbXBsZS4gVGhpcyBkYXRhIGNvbnRhaW5zIGEgd2VpZ2h0aW5nIHZhbHVlIHRvIGFjY291bnQgZm9yIHRoZSBmYWN0IHRoYXQgaW5kaXZpZHVhbHMgYXJlIG5vdCBzYW1wbGVkIHdpdGggZXF1YWwgcHJvYmFiaWxpdHkgKHBlb3BsZSB3aG8gaGF2ZSBhIGdyZWF0ZXIgY2hhbmNlIG9mIGJlaW5nIHNhbXBsZWQgaGF2ZSBhIGxvd2VyIHdlaWdodCB0byByZWZsZWN0IHRoaXMpLiBUaGVzZSBhcmUgZXNzZW50aWFsbHkgYSBmcmVxdWVuY3kgY291bnQgZm9yIGVhY2ggcm93LiAKCkkgZmlsdGVyIG91dCBVUy1ib3JuIHBlb3BsZSBhbmQgdGhlIGluZGl2aWR1YWxzIHdob3NlIHBhcmVudHMgYXJlIEFtZXJpY2FuLiBJbiBvdGhlciB3b3JkcywgSSBuYXJyb3dlZCBkb3duIHRoZSBkYXRhIHRvIHRoZSBwZW9wbGUgd2hvIHdlcmUgYm9ybiBvdXRzaWRlIHRoZSBVbml0ZWQgU3RhdGVzIHdpdGggbm9uLVVTIHBhcmVudHMuIE1vcmVvdmVyLCBJIGZpbHRlcmVkIG15IGRhdGEgdG8gcGVvcGxlIGJldHdlZW4gMjAgYW5kIDcwIHllYXJzIG9sZCwgbm90IGluY2x1ZGluZyA3MC4gVGhlcmUgd2VyZSBhYm91dCAyMDAgdmFyaWFibGVzIGluIHRoZSBvcmlnaW5hbCBkYXRhc2V0LiBJIHBpY2tlZCAxOSB2YXJpYWJsZXMgYXQgZmlyc3QuCgpUaGVzZSB2YXJpYWJsZXMgYXJlOiAKPC9kaXY+CgpgYGB7cn0Kb3JpZ2luYWwuY29sdW1uLm5hbWUgPC0gYygiUFdHVFAiLCAiUE9CUCIsICJXQU9CIiwgIlNFWCIsICJBR0VQIiwgIlNUIiwgIk1BUiIsICJTQ0hMIiwgCiAgICAgICAgICAgICAgICJGT0QxUCIsICJGT0QyUCIsICJJTkRQIiwgIldLSFAiLCAiUElOQ1AiLCAiQURKSU5DIiwgIlBPVlBJUCIsIAogICAgICAgICAgICAgICAiWU9FUCIsICJISUNPViIsICJQUklWQ09WIiwgIlBVQkNPViIpCgpyZW5hbWVkLnRvIDwtIGMoIndlaWdodCIsICJiaXJ0aF9wbGFjZSIsICJiaXJ0aF9jb250aW5lbnQiLCAiZ2VuZGVyIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAiYWdlIiwgInJlc2lkZW50X3N0YXRlIiwgIm1hcml0YWxfc3RhdHVzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAiZWR1Y2F0aW9uYWxfYXR0YWlubWVudCIsICJmaXJzdF9kZWdyZWUiLCAic2Vjb25kX2RlZ3JlZSIsICAKICAgICAgICAgICAgICAgICAgICAgICAgICJpbmR1c3RyeSIsICJ3b3JrZWRfcGVyX3dlZWsiLCAidG90YWxfaW5jb21lIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAiYWRqdXN0X2ZhY3RvciIsICJJbmNvbWVfdG9fcG92ZXJ0eV9yYXRpbyIsICJ5ZWFyX2VudHJ5IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAiaGVhbHRoX2luc3VyYW5jZSIsICJwcml2YXRlX2hlYWx0aF9pbnN1cmFuY2VfYmluIiwKICAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNfaGVhbHRoX2NvdmVyYWdlX2JpbiIpCmthYmxlKGRhdGEuZnJhbWUob3JpZ2luYWwuY29sdW1uLm5hbWUsIHJlbmFtZWQudG8pKQoKYGBgCgoKPGRpdj4KSXQgaXMgd29ydGggbWVudGlvbmluZyB0aGF0IG9ubHkgdGhyZWUgdmFyaWFibGVzIHVuZGVyIGZvciByZXBvcnQgYXJlIHRvcC1jb2RlZCwgYGFnZWAsIGB0b3RhbF9pbmNvbWVgIGFuZCBgaW5jb21lIHRvIHBvdmVydHkgcmF0aW9gLiBUaGUgdG9wIGNvZGUgdGhyZXNob2xkIG9mIHZhcmlhYmxlIGFnZSBpcyBiZWxvdyA5MSwgd2hpY2ggaXMgbm90IGltcG9ydGFudCBzaW5jZSBJIGZpbHRlcmVkIG15IGRhdGEgdG8gdGhlIGFnZSBiZWxvdyA3MC4gT25seSBpbiBhbmFseXppbmcgdGhlIGB0b3RhbF9pbmNvbWVgIHZhcmlhYmxlIHRoZSByZXN1bHQgbWlnaHQgYmUgYWZmZWN0ZWQuIFRoZSB0b3AtY29kZWQgdGhyZXNob2xkIGZvciB0aGlzIHZhcmlhYmxlIGlzICQ0LDIwOSw5OTUuIFRoZSB0b3AgY29kZWQgZm9yIGBpbmNvbWUgdG8gcG92ZXJ0eSByYXRpb2AgaXMgMC41MDEuCgpJIHNlYXJjaGVkIHRocm91Z2ggdGhlIHZhcmlhYmxlIHZhbHVlcyBpbiB0aGUg4oCcMjAxNS0yMDE5IEFDUyBQVU1TIERhdGEgRGljdGlvbmFyeeKAnSBmaWxlIHRvIHNlZSBob3cgSSBjb3VsZCBwcmUtcHJvY2VzcyBteSB2YXJpYWJsZXMgaW4gdGhlIGJlc3Qgd2F5IHBvc3NpYmxlLiBGaXJzdCwgbXV0YXRlZCBhIG5ldyBjb2x1bW4gbmFtZWQg4oCceWVhcuKAnSBiYXNlZCBvbiBgIGFkanVzdF9mYWN0b3IgYC4gVGhlbiBJIGNoYW5nZWQgYHRvdGFsX2luY29tZWAgdG8gdGhlIGBhZGpfaW5jb21lYCB2YXJpYWJsZSBieSBhZGp1c3RpbmcgZm9yIGluZmxhdGlvbiBmb3IgZWFjaCB5ZWFyLiBTb21lIHZhcmlhYmxlcyBuZWVkZWQgbW9yZSB0aW1lIGFuZCBlZmZvcnQgdG8gYWxsb2NhdGUgdGhlbSBwcm9wZXIgZmFjdG9yIGxhYmVsLiBGb3IgaW5zdGFuY2UsIEkgbGFiZWxsZWQgYGVkdWNhdGlvbmFsX2F0dGFpbm1lbnRgIHRvIGZvdXIgZmFjdG9ycy4gQUNTIGNsYXNzaWZpZWQgdGhlIGZpZWxkIG9mIGRlZ3JlZSBpbnRvIDE1IGNhdGVnb3JpZXMuIEkgd2FzIGN1cmlvdXMgYWJvdXQgdGhlIGZpZWxkIG9mIEZpbmFuY2UgYXMgd2VsbC4gU28sIEkgZGl2aWRlZCBhbGwgZmllbGRzIGludG8gMTYgZmllbGRzLiBUaGVuLCBJIGNvbWJpbmVkIHRoZSBgZWR1Y2F0aW9uYWxfYXR0YWlubWVudGAgd2l0aCB0aGUgYGZpcnN0X2RlZ3JlZWAgY29sdW1uIHRvIGBlZHVjYXRpb25gIGNvbHVtbi4KVGhlIGBpbmR1c3RyeWAgY29sdW1uIGhhcyBtb3JlIHRoYW4gMjAwIHZhbHVlcy4gQmFzZWQgb24gdGhlIOKAnDIwMTctaW5kdXN0cnktY29kZS1saXN0LXdpdGgtY3Jvc3N3YWxr4oCdIG9uIGh0dHBzOi8vd3d3LmNlbnN1cy5nb3YsICBJIHJlY29kZWQgdGhlbSB0byAyMiBsZXZlbHMuIApIZWFsdGggSW5zdXJhbmNlIGhhZCB0aHJlZSBzZXBhcmF0ZSBjb2x1bW5zLiBJIGNvbWJpbmVkIGFsbCBvZiB0aGVtIGludG8gb25lIGNvbHVtbiBhbmQgYWxsb2NhdGVkIHRocmVlIHZhbHVlcyDigJxwcml2YXRl4oCdLCDigJxwdWJsaWPigJ0gYW5kIOKAnG5vIGluc3VyYW5jZeKAnS4gSSBjaGFuZ2VkIHRoZSB0eXBlIG9mIGFsbCBvdGhlciB2YXJpYWJsZXMgdG8gZmFjdG9yIGFuZCBzYXZlZCB0aGUgZmlsZSB0byDigJx0aWR5LmRhdGEuUkRhdGHigJ0uIEhlcmUgaXMgdGhlIHN1bW1hcnkgb2YgZGF0YSBhZnRlciBwcmUtcHJvY2Vzc2luZy4KPC9kaXY+CgoKYGBge3J9CiMgTnVtZXJpY2FsIGRhdGEKa2FibGUoc3VtbWFyeShtYWluLmRhdGFbLGMoMSwyLDYsMTIsMTMsMTQsMTUpXSksIGNhcHRpb24gPSAiTnVtZXJpY2FsIGRhdGEiKQojIENhdGVnb3JpY2FsIGRhdGEKa2FibGUoc3VtbWFyeShtYWluLmRhdGFbLGMoMyw0LDUsNyw4LDksMTAsMTEsMTYpXSksIGNhcHRpb24gPSAiQ2F0ZWdvcmljYWwgZGF0YSIpCmBgYAoKCjxkaXY+CgpUaGlzIHN1bW1hcnkgaGFzIHNvbWUgaW50ZXJlc3RpbmcgZmluZGluZ3MuIGB3b3JrZWRfcGVyX3dlZWtgIHZhcmlhYmxlIGhhcyB0aGUgc2FtZSB2YWx1ZSBmb3IgbWVkaWFuIGFuZCAzcmQgcXVhcnRlci4gSSB3aWxsIGV4cGxvcmUgdGhpcyB2YXJpYWJsZSBpbiB0aGUgbmV4dCBzZWN0aW9uLgpNZXhpY28gaGFzIHRoZSBtb3N0IHBvcHVsYXRpb24gb2YgSW1taWdyYW50cyBmb2xsb3dpbmcgYnkgSW5kaWEgYW5kIENoaW5hLiBJIHdpbGwgYWxzbyBlbGFib3JhdGUgb24gdGhlIGRpc3RyaWJ1dGlvbiBvZiBiaXJ0aHBsYWNlIGFjcm9zcyB0aGUgU3RhdGVzLiBBYm91dCBgciBzdW0oaXMubmEobWFpbi5kYXRhJHNlY29uZF9kZWdyZWUpKWAgbWlzc2luZyB2YWx1ZXMgZm9yIGBzZWNvbmQgZGVncmVlYCBzaG93ZWQgYXQgbGVhc3QgYHIgbnJvdyhtYWluLmRhdGEpIC0gc3VtKGlzLm5hKG1haW4uZGF0YSRzZWNvbmRfZGVncmVlKSlgIG9ic2VydmF0aW9ucyBoYXZlIGEgc2Vjb25kIGRlZ3JlZSBvciBiZXlvbmQuIEF0IGxhc3QsIGByIG5yb3coc3Vic2V0KG1haW4uZGF0YSwgbWFpbi5kYXRhJGhlYWx0aF9pbnN1cmFuY2UgPT0gIlByaXZhdGUiKSlgIG9ic2VydmF0aW9ucyBoYXZlIHByaXZhdGUgaW5zdXJhbmNlIGZvbGxvd2VkIGJ5IGByIG5yb3coc3Vic2V0KG1haW4uZGF0YSwgbWFpbi5kYXRhJGhlYWx0aF9pbnN1cmFuY2UgPT0gIlB1YmxpYyIpKWAgcHVibGljIGluc3VyYW5jZSBvYnNlcnZhdGlvbnMuCjwvZGl2PgoKYGBge3J9Cm1haW4uZGF0YSAlPiUKICBmaWx0ZXIoIWlzLm5hKGluZHVzdHJ5KSwgCiAgICAgICAgICFpbmR1c3RyeSA9PSAiVW5lbXBsb3llZCBpbiA1IHllYXJzIiwgCiAgICAgICAgIGFkal9pbmNvbWUgPCA1MDAwMDApICU+JSAKICBnZ3Bsb3QoKSArCiAgYWVzKGFkal9pbmNvbWUpICsKICBnZW9tX2RlbnNpdHkoKSArIAogIHNjYWxlX3lfY29udGludW91cyhuYW1lPSJGcmVxdWVuY3kiLCBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZT0iQWRqdXN0ZWQgaW5jb21lIiwgbGFiZWxzID0gc2NhbGVzOjpjb21tYSkKCmBgYAoKPGRpdj4KVGhpcyBkaWFncmFtIGlzIGEgcmlnaHQtc2tld2VkIGRpc3RyaWJ1dGlvbiBvZiBhZGp1c3RlZCBpbmNvbWUgbGVzcyB0aGFuICQ1MDAsMDAwIHdvcmtpbmcgaW4gdGhlIGluZHVzdHJ5LiBNb3N0IGltbWlncmFudHMgaGF2ZSBhIHNhbGFyeSBvZiBmZXdlciB0aGFuIDEwMCwwMDAgZG9sbGFycy4KPC9kaXY+CgpgYGB7cn0KbWl1IDwtIG1haW4uZGF0YSAlPiUgCiAgZmlsdGVyKCFpcy5uYSh3b3JrZWRfcGVyX3dlZWspKSAlPiUgCiAgc3VtbWFyaXNlKEF2Zy5ob3VyLndvcmtlZCA9IHd0ZC5tZWFuKHdvcmtlZF9wZXJfd2Vlaywgd2VpZ2h0cyA9IHdlaWdodCwgbm9ybXd0ID0gRikpCgojIG51bWJlciBvZiBwZW9wbGUgd2l0aCA0MCBob3VyIHdvcmsgdGltZQoKd29yay5mb3VydHkgPC0gbWFpbi5kYXRhICU+JSAKICAgIGdyb3VwX2J5KHdvcmtlZF9wZXJfd2VlaykgJT4lIAogICAgc3VtbWFyaXNlKGNvdW50ID0gc3VtKHdlaWdodCkpICU+JSAKICBmaWx0ZXIod29ya2VkX3Blcl93ZWVrID09IDQwKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBzZWxlY3QoY291bnQpICU+JSAKICBhcy5udW1lcmljKCkKYGBgCgo8ZGl2Pgp3b3JrZWRfcGVyX3dlZWsgc2hvdWxkIGZvbGxvdyBhIFBvaXNzb24gZGlzdHJpYnV0aW9uLiBJIGFwcGxpZWQgdGhlIHdlaWdodCBvZiBlYWNoIG9ic2VydmF0aW9uIHRvIGdldCBhbiBhY2N1cmF0ZSByZXN1bHQuIFRoZSB3ZWlnaHRlZCBtZWFuIGlzIGByIHJvdW5kKGFzLm51bWVyaWMobWl1KSwgMilgLCBpbmRpY2F0aW5nIHRoYXQgbWFueSBwZW9wbGUgaGF2ZSBhIHR5cGljYWwgNDAtaG91ciB3b3JrIHRpbWUuIFRoZSBmb2xsb3dpbmcgZGlzdHJpYnV0aW9uIHNob3dzIGByIGZvcm1hdCh3b3JrLmZvdXJ0eSwgc2NpZW50aWZpYyA9IE5BKWAgcGVvcGxlIHdvcmtlZCBleGFjdGx5IDQwIGhvdXJzIHBlciB3ZWVrLiAKPC9kaXY+CgoKYGBge3J9Cm1haW4uZGF0YSAlPiUgCiAgZ3JvdXBfYnkod29ya2VkX3Blcl93ZWVrKSAlPiUgCiAgc3VtbWFyaXNlKGNvdW50ID0gc3VtKHdlaWdodCkpICU+JSAKICBnZ3Bsb3QoKSArCiAgYWVzICh3b3JrZWRfcGVyX3dlZWssIGNvdW50KSArCiAgZ2VvbV9oaXN0b2dyYW0oc3RhdCA9ICJpZGVudGl0eSIpICsKICB4bGFiKCJIb3VyIHdvcmtlZCBwZXIgd2VlayIpCiAgCgoKYGBgCgoKCgoKPGRpdj4KSXQgaXMgZXhjaXRpbmcgdG8gZXh0cmFjdCBzb21lIGluZm9ybWF0aW9uIGFib3V0IHRoZSBpbW1pZ3JhbnQncyBkZW1vZ3JhcGhpY3MuIEhvdyB3ZXJlIHRoZSBpbW1pZ3JhbnRzIGRpc3RyaWJ1dGVkIGZyb20gMjAxNSB0byAyMDE5IGFjcm9zcyB0aGUgVVM/IFdoYXQgY291bnRyaWVzIGhhdmUgdGhlIG1vc3QgcG9wdWxhdGlvbiBmb3IgZWFjaCBzdGF0ZT8KPC9kaXY+CgpgYGB7cn0KCm1heF9taWdyYXRlIDwtIG1haW4uZGF0YSAlPiUgCiAgZ3JvdXBfYnkocmVzaWRlbnRfc3RhdGUpICU+JSAKICBzdW1tYXJpc2UocG9wdWxhdGlvbiA9IHN1bSh3ZWlnaHQpKSAKCmxpYnJhcnkodXNtYXApCiMgTm9uLVVTLWJvcm4gcG9wdWxhdGlvbiBieSBzdGF0ZQptYXhfbWlncmF0ZSRmaXBzIDwtIHN0YXRlcG9wJGZpcHMKbWF4X21pZ3JhdGUkYWJiciA8LSBzdGF0ZXBvcCRhYmJyCgpwbG90X3VzbWFwKGRhdGEgPSBtYXhfbWlncmF0ZSwgdmFsdWVzID0gInBvcHVsYXRpb24iLCBhbHBoYSA9IC45LCBsYWJlbHMgPSBUKSArIAogIHNjYWxlX2ZpbGxfY29udGludW91cyhsb3cgPSAid2hpdGUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgaGlnaCA9ICJibHVlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiTm9uLVVTLWJvcm4gcG9wdWxhdGlvbiIsIAogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHNjYWxlczo6Y29tbWEpICsgCiAgbGFicyh0aXRsZSA9ICJOb24tVVMtYm9ybiBwb3B1bGF0aW9uIGJ5IHN0YXRlIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCmBgYAo8ZGl2PgpUaGUgYWJvdmUgVVMgbWFwIHNob3dlZCB3aGljaCBzdGF0ZXMgYXJlIHRoZSBtb3N0IHBvcHVsYXRlZCBpbW1pZ3JhbnRzLiBJIGFwcGxpZWQgYSBwb3B1bGF0aW9uIGxvZ2FyaXRobSB0byBzZWUgd2hpY2ggc3RhdGUgaGFkIHRoZSBsb3dlc3Qgbm9uLVVTLWJvcm4gcGVvcGxlLiBUaGUgYmVsb3cgbWFwIHNob3dzIHRoZXNlIHN0YXRlc+KAmSBwb3B1bGF0aW9ucyBtb3JlIHByZWNpc2VseS4gVGhlIGByIGFzLmNoYXJhY3Rlcihhc192ZWN0b3IoKG1heF9taWdyYXRlICU+JSBzbGljZSh3aGljaC5tYXgocG9wdWxhdGlvbikpKVsxXSkpYCBoYXMgdGhlIG1vc3QgcG9wdWxhdGlvbiBhbW9uZyBhbGwgc3RhdGVzLCB3aXRoIGByIGZvcm1hdChhcy5udW1lcmljKGFzX3ZlY3RvcigobWF4X21pZ3JhdGUgJT4lIHNsaWNlKHdoaWNoLm1heChwb3B1bGF0aW9uKSkpWzJdKSksIHNjaWVudGlmaWMgPSBOQSlgIGltbWlncmFudHMuCjwvZGl2PgoKCmBgYHtyfQojIExvZyBwb3B1bGF0aW9uIG9mIG5vbi1VUy1ib3JuIGJ5IHN0YXRlCm1heF9taWdyYXRlX2xvZyA8LSBtYXhfbWlncmF0ZSAlPiUgCiAgbXV0YXRlKGxvZ19wb3AgPSBsb2cocG9wdWxhdGlvbikpCgpwbG90X3VzbWFwKGRhdGEgPSBtYXhfbWlncmF0ZV9sb2csIHZhbHVlcyA9ICJsb2dfcG9wIiwgYWxwaGEgPSAuOSwgbGFiZWxzID0gVCkgKyAKICBzY2FsZV9maWxsX2NvbnRpbnVvdXMobG93ID0gIndoaXRlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIGhpZ2ggPSAiYmx1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIk5vbi1VUy1ib3JuIGxvZyBwb3B1bGF0aW9uIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gc2NhbGVzOjpjb21tYSkgKyAKICBsYWJzKHRpdGxlID0gIk5vbi1VUy1ib3JuIGxvZyBwb3B1bGF0aW9uIGJ5IHN0YXRlIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCmBgYAoKCgpgYGB7cn0KIyBXaGF0IG5vbi1VUyBjb3VudHJ5IGhhcyB0aGUgbW9zdCBwb3B1bGF0aW9uIGZvciBlYWNoIHN0YXRlPwptYXhfcG9wIDwtIG1haW4uZGF0YSAlPiUgCiAgZ3JvdXBfYnkocmVzaWRlbnRfc3RhdGUsIGJpcnRoX3BsYWNlKSAlPiUgCiAgc3VtbWFyaXNlKHBvcHVsYXRpb24gPSBzdW0od2VpZ2h0KSkgJT4lIAogIHVuZ3JvdXAoYmlydGhfcGxhY2UpICU+JSAKICBzbGljZSh3aGljaC5tYXgocG9wdWxhdGlvbikpCmBgYAoKPGRpdj4KVGhlIGJlbG93IG1hcCBzaG93cyB3aGF0IG5vbi1VUyBjb3VudHJ5IGhhcyB0aGUgbW9zdCBwb3B1bGF0aW9uIGZvciBlYWNoIHN0YXRlLiBJbiBgciBucm93KG1heF9wb3AgJT4lIGZpbHRlcihiaXJ0aF9wbGFjZSA9PSAiTWV4aWNvIikpYCBzdGF0ZXMsIHBlb3BsZSBib3JuIGluIE1leGljbyBoYXZlIHRoZSBtb3N0IHBvcHVsYXRpb24uCjwvZGl2PgoKYGBge3J9Cm1heF9wb3AkZmlwcyA8LSBzdGF0ZXBvcCRmaXBzCm1heF9wb3AkYWJiciA8LSBzdGF0ZXBvcCRhYmJyCgpwbG90X3VzbWFwKGRhdGEgPSBtYXhfcG9wLCB2YWx1ZXMgPSAiYmlydGhfcGxhY2UiLCBhbHBoYSA9IC43LCBsYWJlbHMgPSBUKSArIAogIHNjYWxlX2ZpbGxfZGlzY3JldGUobmFtZSA9ICJDb3VudHJ5IikgICsKICBsYWJzKHRpdGxlID0gIldoYXQgbm9uLVVTIGNvdW50cnkgaGFzIHRoZSBtb3N0IHBvcHVsYXRpb24gZm9yIGVhY2ggc3RhdGU/IikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCgpgYGAKCmBgYHtyfQptYWluLmRhdGEgJT4lIAogIHNlbGVjdCh3ZWlnaHQsIHllYXJfZW50cnkpICU+JSAKICBncm91cF9ieSh5ZWFyX2VudHJ5KSAlPiUgCiAgc3VtbWFyaXNlKFBvcHVsYXRpb24gPSBzdW0od2VpZ2h0KSkgJT4lIAogIGdncGxvdCgpICsKICBhZXMoeWVhcl9lbnRyeSwgUG9wdWxhdGlvbikgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgoKSArCiAgbGFicyh4ID0gIlllYXIgb2YgRW50cnkiLCB0aXRsZSA9ICJOdW1iZXIgb2Ygbm9uLVVTIGJvcm4gcGVvcGxlIGJldHdlZW4gMTk0NS0yMDE5IiApCmBgYAoKPGRpdj4KQXMgd2UgY2FuIHNlZSBmcm9tIHRoZSBhYm92ZSBncmFwaCwgdGhlIG51bWJlciBvZiBub25fVVMgYm9ybiBwZW9wbGUgaGF2ZSBzdXJnZWQgdGlsbCAyMDAwLCBhbmQgd2UgZmFjZWQgdGhlIGRlY3JlYXNlIHVwb24gdGhhdCB0aW1lLgo8L2Rpdj4KCjxkaXY+ClRoZSBjaGFydCBiZWxvdyBzaG93cyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGF2ZXJhZ2UgaW5jb21lIGFkanVzdGVkIGZvciBpbmZsYXRpb24gd2l0aCB0aGUgY29udGluZW50IHRoZXkgd2VyZSBib3JuIGluLiBTb3V0aCBBZnJpY2EgaGFzIHRoZSBsb3dlc3QgYXZlcmFnZSBpbmNvbWUsIGFuZCBOb3J0aCBBbWVyaWNhIGhhcyB0aGUgbW9zdCByZXZlbnVlLgo8L2Rpdj4KCgoKCmBgYHtyfQppbmNvbS5jb250aW5lbnQgPC0gbWFpbi5kYXRhICU+JSAKICBzZWxlY3Qod2VpZ2h0LCBiaXJ0aF9jb250aW5lbnQsIGFkal9pbmNvbWUsIEluY29tZV90b19wb3ZlcnR5X3JhdGlvKSAlPiUgCiAgZ3JvdXBfYnkoYmlydGhfY29udGluZW50KSAlPiUgCiAgc3VtbWFyaXNlKGFkal9pbmNvbWUgPSB3dGQubWVhbihhZGpfaW5jb21lLCB3ZWlnaHRzID0gd2VpZ2h0LCBub3Jtd3QgPSBGKSkgJT4lIAogIGFycmFuZ2UoZGVzYyhhZGpfaW5jb21lKSkKCmluY29tLmNvbnRpbmVudCAlPiUgCiAgZ2dwbG90KCkgKwogIGFlcyhmY3RfcmVvcmRlcihiaXJ0aF9jb250aW5lbnQsIGFkal9pbmNvbWUpLCBhZGpfaW5jb21lLCBmaWxsID0gYmlydGhfY29udGluZW50KSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBsYWJzKHggPSAiQmlydGggY29udGluZW50IiwgeSA9ICJJbmNvbWUgYWRqdXN0ZWQgZm9yIGluZmxhdGlvbiIpCmBgYAoKYGBge3J9CnRvcC5pbmNvbWUgPC0gbWFpbi5kYXRhICU+JSAKICBzZWxlY3Qod2VpZ2h0LCBiaXJ0aF9wbGFjZSwgYWRqX2luY29tZSwgSW5jb21lX3RvX3BvdmVydHlfcmF0aW8pICU+JSAKICBncm91cF9ieShiaXJ0aF9wbGFjZSkgJT4lIAogIHN1bW1hcmlzZShhZGpfaW5jb21lID0gd3RkLm1lYW4oYWRqX2luY29tZSwgd2VpZ2h0cyA9IHdlaWdodCwgbm9ybXd0ID0gRikpICU+JSAKICAgICAgICAgICAgICBhcnJhbmdlKGRlc2MoYWRqX2luY29tZSkpICU+JSAKICBoZWFkKDIwKSAKdG9wLmluY29tZSAlPiUKICBrYWJsZShjYXB0aW9uID0gIlRvcCAyMCBhdmVyYWdlIGluY29tZSBnZW5lcmF0ZWQgYnkgbm9uLVVTIGJvcm4gcGVvcGxlIikKYGBgCgo8ZGl2PgpBdXN0cmFsaWFuIHBlb3BsZSBoYXZlIHRoZSBtb3N0IGF2ZXJhZ2UgaW5jb21lLCB3aXRoICBgciBmb3JtYXQocm91bmQodG9wLmluY29tZSRhZGpfaW5jb21lWzFdKSwgc2NpZW50aWZpYyA9IE5BKWAgYW5udWFsbHkuIEFzIHNlZW4gaW4gdGhlIHRhYmxlLCBCcml0aXNoIGFuZCBTY2FuZGluYXZpYW5zIGFyZSBoaWdoIGVhcm5lciBpbiB0aGUgVVMuCjwvZGl2PgoKYGBge3J9CmZvcmVpZ25fR0RQIDwtIG1haW4uZGF0YSAlPiUgCiAgc2VsZWN0KHdlaWdodCwgYmlydGhfcGxhY2UsIGFkal9pbmNvbWUsIEluY29tZV90b19wb3ZlcnR5X3JhdGlvKSAlPiUgCiAgZ3JvdXBfYnkoYmlydGhfcGxhY2UpICU+JSAKICBzdW1tYXJpc2UoYWRqX2luY29tZSA9IHd0ZC5tZWFuKGFkal9pbmNvbWUsIHdlaWdodHMgPSB3ZWlnaHQsIG5vcm13dCA9IEYpLCAKICAgICAgICAgICAgcG9wdWxhdGlvbiA9IHN1bSh3ZWlnaHQpLCBHRFAgPSBhZGpfaW5jb21lKnBvcHVsYXRpb24vMTBeKDkpKSAlPiUgCiAgYXJyYW5nZShkZXNjKEdEUCkpICU+JSAKICBoZWFkKDIwKQprYWJsZShmb3JlaWduX0dEUCwgY2FwdGlvbiA9ICJUb3AgMjAgVVMgR0RQIGdlbmVyYXRlZCBieSBub24tVVMgYm9ybiBwZW9wbGUiKQogIApgYGAKCjxkaXY+CkJ5IG11bHRpcGx5aW5nIGFkanVzdGVkIGluY29tZSBhbmQgcG9wdWxhdGlvbiBmb3IgZWFjaCBjb3VudHJ5LCBJIGNyZWF0ZWQgYSBuZXcgY29sdW1uLCBHRFAsIHdoaWNoIHN0YW5kcyBmb3IgR3Jvc3MgRG9tZXN0aWMgUGVyaW9kLiBNZXhpY2FuIHdpdGggYHIgcm91bmQoZm9yZWlnbl9HRFAkR0RQWzFdKWAgYmlsbGlvbiBkb2xsYXJzIGNvbnRyaWJ1dGVkIHRoZSBtb3N0IGFtb25nIGFsbCBuYXRpb25zIGZvciBnZW5lcmF0aW5nIFVTIEdEUCBzaW5jZSB0aGV5IGhhdmUgdGhlIG1vc3QgcG9wdWxhdGlvbiBpbiB0aGUgVVMsIGZvbGxvd2VkIGJ5IEluZGlhIGFuZCBDaGluYS4KPC9kaXY+CgpgYGB7cn0KYXNpYW5fR0RQIDwtIG1haW4uZGF0YSAlPiUgCiAgZmlsdGVyKGJpcnRoX2NvbnRpbmVudCA9PSAiQXNpYSIpICU+JSAKICBzZWxlY3Qod2VpZ2h0LCBiaXJ0aF9wbGFjZSwgYWRqX2luY29tZSwgSW5jb21lX3RvX3BvdmVydHlfcmF0aW8pICU+JSAKICBncm91cF9ieShiaXJ0aF9wbGFjZSkgJT4lIAogIHN1bW1hcmlzZShhZGpfaW5jb21lID0gd3RkLm1lYW4oYWRqX2luY29tZSwgd2VpZ2h0cyA9IHdlaWdodCwgbm9ybXd0ID0gRiksIAogICAgICAgICAgICBwb3B1bGF0aW9uID0gc3VtKHdlaWdodCksIEdEUCA9YWRqX2luY29tZSpwb3B1bGF0aW9uLzEwXig5KSkgJT4lIAogIGFycmFuZ2UoZGVzYyhHRFApKSAlPiUgCiAgaGVhZCgyMCkKa2FibGUoYXNpYW5fR0RQLCBjYXB0aW9uID0gIlRvcCAyMCBVUyBHRFAgZ2VuZXJhdGVkIGJ5IEFzaWFuIGJvcm4gcGVvcGxlIikKYGBgCgo8ZGl2PgpUaGUgYWJvdmUgdGFibGUgaXMgdGhlIHNhbWUgZGF0YSBidXQgZmlsdGVyZWQgZG93biB0byBBc2lhLiBUaGUgc2hhcmUgb2YgSW5kaWEgZm9yIGdlbmVyYXRpbmcgR0RQIHdpdGggYHIgcm91bmQoYXNpYW5fR0RQJEdEUFsxXSlgIGJpbGxpb24gZG9sbGFycyBpbiB0aGUgVVMgaXMgbW9yZSB0aGFuIHR3aWNlIG9mIHRoZSBzZWNvbmQgY291bnRyeSwgQ2hpbmEgd2l0aCBgciByb3VuZChhc2lhbl9HRFAkR0RQWzJdKWAgYmlsbGlvbiBkb2xsYXJzLgo8L2Rpdj4KCmBgYHtyfQpwb3ZlcnR5LnJhdGlvIDwtIG1haW4uZGF0YSAlPiUgCiAgc2VsZWN0KHdlaWdodCwgYmlydGhfcGxhY2UsIGFkal9pbmNvbWUsIEluY29tZV90b19wb3ZlcnR5X3JhdGlvKSAlPiUgCiAgZ3JvdXBfYnkoYmlydGhfcGxhY2UpICU+JSAKICBzdW1tYXJpc2UoSW5jb21lX3RvX3BvdmVydHlfcmF0aW8gPSB3dGQubWVhbihJbmNvbWVfdG9fcG92ZXJ0eV9yYXRpbywgd2VpZ2h0cyA9IHdlaWdodCwgbm9ybXd0ID0gRiksIAogICAgICAgICAgICBwb3B1bGF0aW9uID0gc3VtKHdlaWdodCkpICU+JSAKICBhcnJhbmdlKGRlc2MoSW5jb21lX3RvX3BvdmVydHlfcmF0aW8pKSAlPiUgCiAgaGVhZCgyMCkKYGBgCgo8ZGl2PgpUaGUgdGFibGUgYmVsb3cgc2hvd3MgdGhhdCB0aGUgbW9zdCBlYXJuZXIgcGVvcGxlIGhhdmUgdGhlIGJlc3QgaW5jb21lIHRvIHBvdmVydHkgcmF0aW8uIFRoZSBVbml0ZWQgS2luZ2RvbSBpcyB0aGUgZmlyc3QgY291bnRyeSBvbiB0aGlzIGxpc3Qgd2l0aCBhbiBpbmNvbWUgdG8gcG92ZXJ0eSByYXRpbyBvZiBgciByb3VuZChwb3ZlcnR5LnJhdGlvJEluY29tZV90b19wb3ZlcnR5X3JhdGlvWzFdKWAuCjwvZGl2PgoKYGBge3J9CmthYmxlKHBvdmVydHkucmF0aW8sIGNhcHRpb24gPSAiVG9wIDIwIGNvdW50cmllcyBpbiB0ZXJtcyBvZglpbmNvbWUgdG8gcG92ZXJ0eSByYXRpbyIpCgpgYGAKCgoKYGBge3J9CmhlYWx0aF9pbnN1cmFuY2UgPC0gbWFpbi5kYXRhICU+JSAKICBncm91cF9ieShoZWFsdGhfaW5zdXJhbmNlKSAlPiUgCiAgc3VtbWFyaXNlKGNvdW50ID0gc3VtKHdlaWdodCkpCmBgYAoKPGRpdj4KQWJvdXQgYHIgcm91bmQoaGVhbHRoX2luc3VyYW5jZSRjb3VudFsyXS9zdW0oaGVhbHRoX2luc3VyYW5jZSRjb3VudCkqMTAwKWAlIG9mIGltbWlncmFudHMgaGF2ZSBwcml2YXRlIGluc3VyYW5jZSBhbmQgIGByIHJvdW5kKGhlYWx0aF9pbnN1cmFuY2UkY291bnRbM10vc3VtKGhlYWx0aF9pbnN1cmFuY2UkY291bnQpKjEwMClgJSBoYXZlIHB1YmxpYyBpbnN1cmFuY2UsIGFuZCB0aGUgcmVzdCBoYXZlIG5vIGluc3VyYW5jZS4gVGhlIGJlbG93IGNoYXJ0IHNob3dzIHRoaXMgZGlzdHJpYnV0aW9uIGluIGEgYmFyIGNoYXJ0IGRpYWdyYW0uCjwvZGl2PgpgYGB7cn0KaGVhbHRoX2luc3VyYW5jZSAlPiUgCiAgZ2dwbG90KCkgKwogIGFlcyhoZWFsdGhfaW5zdXJhbmNlLCBjb3VudCwgZmlsbCA9IGhlYWx0aF9pbnN1cmFuY2UpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIHhsYWIoIkhlYWx0aCBJbnN1cmFuY2UiKQpgYGAKCgojIE1ldGhvZG9sb2d5Cgo8ZGl2PgpJbiB0aGlzIHByb2plY3QsIEkgaW52ZXN0aWdhdGVkIHR3byBkaWZmZXJlbnQgc3R1ZGllcyBpbiBteSBkYXRhLgoKMS0gSSB0cmllZCByZWdyZXNzaW5nIGFkanVzdGVkIGluY29tZSBvbiBpbW1pZ3JhbnRzJyBhZ2UgYW5kIGdlbmRlci4KCjItIFdoaWNoIHR5cGUgb2YgaGVhbHRoIGluc3VyYW5jZSwgaS5lLiwgcHVibGljLCBwcml2YXRlIG9yIHdpdGhvdXQgaW5zdXJhbmNlLCBpcyBzdWl0YWJsZSBmb3Igd2hhdCBjbGFzcyBvZiBpbW1pZ3JhbnRzPyAKCkVhY2ggb2JzZXJ2YXRpb24gaGFkIGEgd2VpZ2h0IGFzc2lnbmVkIHRvIGl0LiBJbiBhbGwgbXkgYW5hbHlzZXMsIEkgY29uc2lkZXIgd2VpZ2h0IHRvIGdldCBhbiBhY2N1cmF0ZSByZXN1bHQgb2YgbXkgZGF0YS4gSSBhbHNvIGFwcGxpZWQgdGhlIGFkanVzdGVkIGZhY3RvciBpbnRvIHRoZSB0b3RhbCBpbmNvbWUgdmFyaWFibGUgdG8gcmVmbGVjdCB0aGUgZWZmZWN0IG9mIGluZmxhdGlvbiBmcm9tIDIwMTUgdG8gMjAxOS4gSSBpbmNsdWRlZCBvdXRsaWVycyBpbiBteSBhbmFseXNpcyB0byBhdm9pZCBtaXNzaW5nIGFueSBleHBsYW5hdGlvbiBwb3dlci4gTW9yZW92ZXIsIEkgcmVtb3ZlZCBhbGwgdGhlIG9ic2VydmF0aW9ucyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluIG15IHJlc2VhcmNoIHRvIGdldCBhIHJlbGV2YW50IHJlc3VsdC4gSSBuZWVkZWQgdG8gY2hlY2sgdGhlIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHRoZSBudW1lcmljIGRhdGEgZm9yIHRoZSBjb2xsaW5lYXJpdHkgaXNzdWVzLgoKRm9yIHRoZSBmaXJzdCBzdHVkeSwgSSBhdmVyYWdlZCBhbGwgdGhlIGluY29tZSBkYXRhIGJ5IGFnZSBhbmQgZ2VuZGVyIHRvIGVhc2lseSBzZWUgdGhlIGRhdGEgdHJlbmQuIEkgZXhhbWluZWQgd2hldGhlciwgYnkgYWRkaW5nIGdlbmRlciwgb3VyIG1vZGVsIHdvdWxkIGltcHJvdmUgaW4gZml0IG9yIG5vdC4gVGhlbiwgSSBzdHVkaWVkIHRoZSBlZmZlY3Qgb2YgdGhlIGludGVyYWN0aW9uIG9mIGFnZSBhbmQgZ2VuZGVyIG9uIGFkanVzdGVkIGluY29tZSBhbmQgd2hldGhlciB0aGUgaW1wcm92ZW1lbnQgaW4gbW9kZWwgZml0IGlzIHdvcnRoIHRoZSBpbmNyZWFzZWQgY29tcGxleGl0eSBvZiBvdXIgbW9kZWwuIERvZXMgYWRkaW5nIHRoZSBpbnRlcmFjdGlvbiB0ZXJtIHByb3ZpZGUgYWRkaXRpb25hbCBleHBsYW5hdG9yeSBwb3dlciBvdmVyIHRoZSBzaW1wbGVyIG1vZGVsPwpUaGUgZGF0YSBoYXMgYW4gYXBwYXJlbnQgbm9ubGluZWFyaXR5LCBzbyBJIGNvbnNpZGVyZWQgdGhlIHNlY29uZC1vcmRlciBvZiBhZ2UgaW4gbXkgbW9kZWwuIEkgY29tcGFyZWQgYWxsIG15IG1vZGVsJ3MgQUlDIHRvIHBpY2sgdGhlIGxvd2VzdCBBSUMgYXMgdGhlIGJlc3QgbW9kZWwuCgpGb3IgdGhlIHNlY29uZCBzdHVkeSwgSSBleGFtaW5lZCBob3cgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGhlYWx0aCBpbnN1cmFuY2Ugd2l0aCBvdGhlciB2YXJpYWJsZXMgaXMgaW4gbXkgZGF0YS4gSSBhcHBsaWVkIGEgY2hpLXNxdWFyZSB0ZXN0IHRvIHNlZSBhbnkgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGhlYWx0aCBpbnN1cmFuY2UgdHlwZSBhbmQgdGhlIGNvbnRpbmVudCB3aGVyZSBpbW1pZ3JhbnRzIHdlcmUgYm9ybi4gSW4gYWRkaXRpb24sIEkgbW9kZWxsZWQgYSBtdWx0aXBsZSBsb2dpc3RpYyByZWdyZXNzaW9uIHRvIHByZWRpY3QgdGhlIG1vZGVsIHdpdGggdGhlIHZhcmlhYmxlcy4gVGhlIGZpcnN0IG1vZGVsIHdhcyBjb21wbGV4LCBhbmQgaXQgdG9vayBzbyBtdWNoIHRpbWUgdG8gYnVpbGQgYSBtb2RlbC4gVG8gc2ltcGxpZnkgbXkgbW9kZWwsIEkgdHJpZWQgdG8gYWdncmVnYXRlIGVkdWNhdGlvbiBhbmQgaW5kdXN0cnkgdmFyaWFibGVzIHdpdGggbWFueSB2YWx1ZXMgaW50byBhbiBhcHByb3ByaWF0ZSBudW1iZXIgb2YgY2x1c3RlcnMuIEkgaW1wbGVtZW50ZWQgay1tZWFuIGNsdXN0ZXJpbmcsIGVsYm93LCBhbmQgU2lsaG91ZXR0ZSBhbmFseXNpcyB0byBmaW5kIHRoZSBiZXN0IGsgKG51bWJlciBvZiBjbHVzdGVycykuIFRoZW4gSSBnZW5lcmF0ZWQgYSBtdWx0aXBsZSBsb2dpc3RpYyByZWdyZXNzaW9uIHdpdGggdGhlIHNpbXBsaWZpZWQgbW9kZWwuCgo8L2Rpdj4KCgojIEZpbmRpbmdzCgojIyBDb3JyZWxhdGlvbgpgYGB7ciBjYWNoZSA9IFRSVUV9CiMgQ29ycmVsYXRpb24gbWF0cml4OiAKY29yLmRhdGEgPC0gbWFpbi5kYXRhICU+JSAKICBzZWxlY3QoeWVhciwgYWdlLCB3b3JrZWRfcGVyX3dlZWssIGFkal9pbmNvbWUsIAogICAgICAgICBJbmNvbWVfdG9fcG92ZXJ0eV9yYXRpbywgeWVhcl9lbnRyeSkKY29yLm1hdCA8LSBtYXRyaXgoMCxsZW5ndGgoY29yLmRhdGEpLGxlbmd0aChjb3IuZGF0YSkpCmZvciAoaSBpbiAxOmxlbmd0aChjb3IuZGF0YSkpIHsKICBmb3IgKGogaW4gMTpsZW5ndGgoY29yLmRhdGEpKSB7CiAgICBjb3IubWF0W2ksal0gPSB3dGQuY29yKGNvci5kYXRhWyxpXSwgY29yLmRhdGFbLGpdLCB3ZWlnaHQgPSBtYWluLmRhdGEkd2VpZ2h0KVsxXQogICAgCiAgfQp9Cgpjb2xuYW1lcyhjb3IubWF0KSA8LSBjKCJZZWFyIiwgIkFnZSIsICJIb3VyIHdvcmtlZC93ZWVrIiwgIkFkanVzdGVkIGluY29tZSIsIAogICAgICAgICAiSW5jb21lIHRvIHBvdmVydHkgcmF0aW8iLCAiWWVhciBvZiBlbnRyeSIpCgpyb3duYW1lcyhjb3IubWF0KSA8LSBjKCJZZWFyIiwgIkFnZSIsICJIb3VyIHdvcmtlZC93ZWVrIiwgIkFkanVzdGVkIGluY29tZSIsIAogICAgICAgICAiSW5jb21lIHRvIHBvdmVydHkgcmF0aW8iLCAiWWVhciBvZiBlbnRyeSIpCgprYWJsZShjb3IubWF0KQpgYGAKCjxkaXY+ClRoaXMgY29ycmVsYXRpb24gbWF0cml4IHNob3dzIGFsbCB0aGUgY29ycmVsYXRpb25zIGJldHdlZW4gdGhlIG51bWVyaWNhbCB2YXJpYWJsZXMuIEFzIHNlZW4sIHRoZSB2YXJpYWJsZSAieWVhciBvZiB0aGUgZW50cnkiIG5lZ2F0aXZlbHkgY29ycmVsYXRlcyB3aXRoIG1vc3Qgb2YgdGhlIHZhcmlhYmxlcy4gImluY29tZSB0byBwb3ZlcnR5IHJhdGlvIiBhbmQgImFkanVzdGVkIGluY29tZSIgdmFyaWFibGVzIGFyZSBhYm91dCBgciByb3VuZChjb3IubWF0WyJBZGp1c3RlZCBpbmNvbWUiLCJJbmNvbWUgdG8gcG92ZXJ0eSByYXRpbyJdKjEwMCkvMTAwYCBjb3JyZWxhdGVkLCB3aGljaCBpcyB0aGUgbW9zdCBoaWdoZXN0IGNvcnJlbGF0aW9uIGJldHdlZW4gYWxsIHRoZSBzdHVkeSB2YXJpYWJsZXMuIEluIGFkZGl0aW9uLCB0aGUgaGVhdCBtYXAgdmlzdWFsaXplcyB0aGUgd2VpZ2h0ZWQgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgc2VsZWN0ZWQgbnVtZXJpY2FsIHZhcmlhYmxlcyBpbiBhIG1vcmUgYmVhdXRpZnVsIGZvcm1hdC4KPC9kaXY+CgpgYGB7cn0KY29ycnBsb3QoY29yLm1hdCwgbWV0aG9kPSJjb2xvciIpCgpgYGAKIyMgSW5jb21lIH4gYWdlLCBnZW5kZXIKCmBgYHtyfQojIHdlaWdodGVkIHQtdGVzdAptYWxlLmluY29tZSA8LSBtYWluLmRhdGEgJT4lIAogIGZpbHRlcihnZW5kZXIgPT0gIk1hbGUiKSAlPiUgCiAgc2VsZWN0KHdlaWdodCwgYWRqX2luY29tZSkKCmZlbWFsZS5pbmNvbWUgPC0gbWFpbi5kYXRhICU+JSAKICBmaWx0ZXIoZ2VuZGVyID09ICJGZW1hbGUiKSAlPiUgCiAgc2VsZWN0KHdlaWdodCwgYWRqX2luY29tZSkKCnRfdGVzdF9zYWxhcnkgPC0gd3RkLnQudGVzdChtYWxlLmluY29tZSRhZGpfaW5jb21lLCAKICAgICAgICAgICBmZW1hbGUuaW5jb21lJGFkal9pbmNvbWUsIAogICAgICAgICAgIG1hbGUuaW5jb21lJHdlaWdodCwgCiAgICAgICAgICAgZmVtYWxlLmluY29tZSR3ZWlnaHQsIAogICAgICAgICAgIG1lYW4xID0gRkFMU0UpCmBgYAoKPGRpdj4KSSBleGFtaW5lZCB0aGUgd2VpZ2h0ZWQgdC10ZXN0IHRvIHNlZSBpcyB0aGVyZSBhbnkgc3RhdGlzdGljYWwgZGlmZmVyZW5jZSBpbiBhdmVyYWdlIHNhbGFyeSBiZXR3ZWVuIG1lbiBhbmQgd29tZW4uIFRoZSBhdmVyYWdlIG1lbidzIHNhbGFyeSBpcyBgciBjdXJyZW5jeShhcy5udW1lcmljKHJvdW5kKHRfdGVzdF9zYWxhcnkkYWRkaXRpb25hbFsyXSkpLCBkaWdpdHMgPSAwKWAgYW5kIGByIGN1cnJlbmN5KGFzLm51bWVyaWMocm91bmQodF90ZXN0X3NhbGFyeSRhZGRpdGlvbmFsWzNdKSksIGRpZ2l0cyA9IDApYCB3b21lbi4gSW4gaW1taWdyYW50IHBlb3BsZSwgd2UgY2FuIGNvbmNsdWRlIHRoYXQgbWVuIGFuZCB3b21lbiBoYXZlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgYXZlcmFnZSB3YWdlcyAodC1zdGF0cyA9IGByIGFzLm51bWVyaWMocm91bmQodF90ZXN0X3NhbGFyeSRjb2VmZmljaWVudHNbM10pKWApLgo8L2Rpdj4KCgo8ZGl2PgpUbyBpbnZlc3RpZ2F0ZSBtb3JlIGFib3V0IHRoZSBhdmVyYWdlIHNhbGFyeSBiZXR3ZWVuIGltbWlncmFudHMsIEkgYWRkZWQgYWdlIGFzIGEgbmV3IHZhcmlhYmxlIHRvIHNob3cgdGhlIGluY29tZSBkaXN0cmlidXRpb24gYWNyb3NzIHRoZSBhZ2UgYXMgd2VsbCBhcyBnZW5kZXIuCjwvZGl2PgpgYGB7cn0KYXZnLmluY29tZS5kYXRhIDwtIG1haW4uZGF0YSAlPiUgCiAgZ3JvdXBfYnkoYWdlLCBnZW5kZXIpICU+JSAKICBzdW1tYXJpc2UoIkF2ZXJhZ2UgaW5jb21lIiA9IHd0ZC5tZWFuKGFkal9pbmNvbWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0cyA9IHdlaWdodCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3Jtd3QgPSBGQUxTRSkpCgphdmcuaW5jb21lLmRhdGEgJT4lIAogIGdncGxvdCgpICsgCiAgYWVzKGFnZSwgYEF2ZXJhZ2UgaW5jb21lYCwgY29sb3IgPSBnZW5kZXIpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UpCmBgYAo8ZGl2PgpUaGUgYWJvdmUgZ3JhcGggc2hvd3MgdGhlIHNjYXR0ZXIgcGxvdCBvZiBpbW1pZ3JhbnRzJyBhdmVyYWdlIHNhbGFyeSBieSBhZ2UgYW5kIGdlbmRlci4gU2FsYXJpZXMgcm9zZSB1bnRpbCB0aGUgYWdlIDQ1IHRvIDUwIGFuZCB0aGVuIHdlIHNlZSBhIGRlY2xpbmUgaW4gd2FnZSBhZnRlciB0aGF0IGFnZS4gQXMgc2VlbiBmcm9tIHRoZSBncmFwaCwgbWVuIGhhdmUgb24gYXZlcmFnZSBtb3JlIHNhbGFyeSB0aGFuIHdvbWVuIGluIGV2ZXJ5IGFnZSBncm91cC4KPC9kaXY+CgoKYGBge3J9CmFnZS5tb2RlbCA8LSBsbShgQXZlcmFnZSBpbmNvbWVgIH4gYWdlLCBhdmcuaW5jb21lLmRhdGEpCnN1bW1hcnkuYWdlIDwtIHN1bW1hcnkoYWdlLm1vZGVsKQpBSUMxIDwtIEFJQyhhZ2UubW9kZWwpCmBgYAoKPGRpdj4KRmlyc3QsIEkgYnVpbHQgYSBtb2RlbCBvZiBpbmNvbWUgcmVncmVzc2VkIGJ5IGFnZS4gVGhlIEFkanVzdGVkIFItc3F1YXJlZCBpcyBgciByb3VuZChzdW1tYXJ5LmFnZSRhZGouci5zcXVhcmVkKjEwMCwxKS8xMDBgIHdoaWNoIGlzIHZlcnkgbG93LiBUaGUgQUlDIGlzIGByIHJvdW5kKEFJQzEpYC4gSSBwbG90dGVkIHRoZSByZXNpZHVhbCB2YWx1ZXMgdG8gc2VlIHdoaWNoIGlzc3VlcyB3ZSBoYWQgaW4gdGVybXMgb2Ygbm9ybWFsaXR5LCBjb25kaXRpb25hbCBoZXRlcm9za2VkYXN0aWNpdHkgYW5kIGVmZmVjdCBvZiBvdXRsaWVycyBpbiBvdXIgc3R1ZHkuIEFzIHNlZW4sIHRoZSByZXN1bHRzIGFyZSBub3QgdmVyeSBwcm9taXNpbmcuCjwvZGl2PgpgYGB7cn0KcGFyKG1mcm93PWMoMiwyKSkKcGxvdChhZ2UubW9kZWwpCgpgYGAKCgoKYGBge3J9CmdlbmRlci5hZ2UubW9kZWwgPC0gbG0oYEF2ZXJhZ2UgaW5jb21lYCB+IGdlbmRlciArIGFnZSAtMSwgYXZnLmluY29tZS5kYXRhKQpzdW1tYXJ5LmdlbmRlciA8LSBzdW1tYXJ5KGdlbmRlci5hZ2UubW9kZWwpCkFJQzIgPC0gQUlDKGdlbmRlci5hZ2UubW9kZWwpCmBgYAoKCgoKYGBge3J9CmNvbXBhcmUuZ2VuZGVyLmFkZGVkIDwtIGFub3ZhKGFnZS5tb2RlbCwgZ2VuZGVyLmFnZS5tb2RlbCkKYGBgCjxkaXY+CkkgYWRkZWQgZ2VuZGVyIHRvIHNlZSBpZiBvdXIgbW9kZWwgd291bGQgaW1wcm92ZSBpbiBmaXQgb3Igbm90LiBJIHVzZWQgQU5PVkEgdG8gY29tcGFyZSB0aGUgcHJldmlvdXMgbW9kZWwgdG8gdGhpcyBtb2RlbC4gVGhpcyBvdXRwdXQgdGVsbHMgdXMgdGhhdCB0aGUgZ2VuZGVyIHZhcmlhYmxlIGlzIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQuIEluIG90aGVyIHdvcmRzLCBpdCBpcyB1bmxpa2VseSB0aGF0IHRoZSBpbXByb3ZlbWVudCBpbiBmaXQgd2hlbiBhZGRpbmcgdGhlIGdlbmRlciB2YXJpYWJsZSBpcyBzaW1wbHkgZHVlIHRvIHJhbmRvbSBmbHVjdHVhdGlvbnMgaW4gdGhlIGRhdGEgRiA9IGByIHJvdW5kKGNvbXBhcmUuZ2VuZGVyLmFkZGVkJEZbMl0sMilgLiBBbHNvLCB0aGUgQUlDIGZvciB0aGlzIG1vZGVsIHdhcyBgciByb3VuZChBSUMyKWAsIHdoaWNoIHdhcyBsZXNzIHRoYW4gdGhlIHByZXZpb3VzIG9uZS4gQXQgbGFzdCwgSSBjaGVja2VkIHRoZSByZXNpZHVhbHMsIGFuZCBhcyBpdCBpcyBzZWVuLCBvbmx5IHRoZSBOb3JtYWwgUS1RIHBsb3QgaGFzIGVuaGFuY2VkLgo8L2Rpdj4KCmBgYHtyfQpwYXIobWZyb3c9YygyLDIpKQpwbG90KGdlbmRlci5hZ2UubW9kZWwpCmBgYAoKYGBge3J9CmludGVyYWN0Lm1vZGVsIDwtIGxtKGBBdmVyYWdlIGluY29tZWAgfiBnZW5kZXIgKiBhZ2UsIGF2Zy5pbmNvbWUuZGF0YSkKc3VtbWFyeS5pbnRlcmFjdCA8LSBzdW1tYXJ5KGludGVyYWN0Lm1vZGVsKQpBSUMzIDwtIEFJQyhpbnRlcmFjdC5tb2RlbCkKYGBgCgoKCmBgYHtyfQpjb21wYXJlLmludGVyYWN0aW9uIDwtIGFub3ZhKGdlbmRlci5hZ2UubW9kZWwsIGludGVyYWN0Lm1vZGVsKQpwX3ZhbHVlX2ludGVyYWN0IDwtIGNvbXBhcmUuaW50ZXJhY3Rpb24kYFByKD5GKWBbMl0KYGBgCgo8ZGl2PgpUaGVuLCBJIGFkZGVkIHRoZSBlZmZlY3Qgb2YgdGhlIGludGVyYWN0aW9uIG9mIGFnZSBhbmQgZ2VuZGVyIG9uIHNhbGFyeSBhbmQgd2hldGhlciB0aGUgaW1wcm92ZW1lbnQgaW4gbW9kZWwgZml0IGlzIHdvcnRoIHRoZSBpbmNyZWFzZWQgY29tcGxleGl0eSBvZiBvdXIgbW9kZWwuIEFOT1ZBIHRlc3QgdGVsbHMgdXMgdGhhdCBhZGRpbmcgaW50ZXJhY3Rpb24gZWZmZWN0IGlzIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQsIEYgPSBgciByb3VuZChjb21wYXJlLmludGVyYWN0aW9uJEZbMl0sMilgLCBwLXZhbHVlID0gYHIgZm9ybWF0KHBfdmFsdWVfaW50ZXJhY3QsIGRpZ2l0cyA9IDIpYC4gVGhlIEFJQyBmb3IgdGhpcyBtb2RlbCB3YXMgYHIgcm91bmQoQUlDMylgLCBzbGlnaHRseSBsZXNzIHRoYW4gdGhlIHByZXZpb3VzIG9uZS4gVG8gc2VlIGlmIHRoZSBjb25kaXRpb25hbCBoZXRlcm9za2VkYXN0aWNpdHkgaW1wcm92ZWQsIEkgcGxvdHRlZCByZXNpZHVhbHMgYW5kIGl0IHNob3dzIG5vIGltcHJvdmVtZW50Lgo8L2Rpdj4KYGBge3J9CnBhcihtZnJvdz1jKDIsMikpCnBsb3QoaW50ZXJhY3QubW9kZWwpCgpgYGAKCgoKYGBge3J9Cgpwb2x5Lm1vZGVsIDwtIGxtKGBBdmVyYWdlIGluY29tZWAgfiAgZ2VuZGVyICogcG9seShhZ2UsIDIpLCBhdmcuaW5jb21lLmRhdGEpCgpwb2x5Lm1vZGVsLnN1bW1hcnkgPC0gc3VtbWFyeShwb2x5Lm1vZGVsKQpBSUM0IDwtIEFJQyhwb2x5Lm1vZGVsKQpgYGAKCgo8ZGl2PgpUaGUgZGF0YSBoYXMgYW4gZXZpZGVudCBub25saW5lYXJpdHksIHNvIEkgZXhhbWluZWQgdGhlIHNlY29uZC1vcmRlciBwb2x5bm9taWFsIG9mIGFnZSB3aXRoIHRoZSBpbnRlcmFjdGlvbiBlZmZlY3QgYnkgZ2VuZGVyLiBIZXJlIGFyZSB0aGUgY29lZmZpY2llbnRzIGZvciB0aGlzIG1vZGVsOgo8L2Rpdj4KCmBgYHtyfQpwb2x5Lm1vZGVsLnJvd25hbWUgPC0gYygiSW50ZXJjZXB0IiwgImdlbmRlckZlbWFsZSIsICAiYWdlIiwgImFnZV4yIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmFjdChhZ2UsZ2VuZGVyKSIsICJpbnRlcmFjdChhZ2VeMixnZW5kZXIpIikKcm93bmFtZXMocG9seS5tb2RlbC5zdW1tYXJ5JGNvZWZmaWNpZW50cykgPC0gcG9seS5tb2RlbC5yb3duYW1lCmthYmxlKHBvbHkubW9kZWwuc3VtbWFyeSRjb2VmZmljaWVudHMsIGRpZ2l0cyA9IGMoMiwyLDIsMiksIGZvcm1hdCA9ICJtYXJrZG93biIgKQpgYGAKCgpgYGB7cn0KY29tcGFyZS5wb2x5IDwtIGFub3ZhKHBvbHkubW9kZWwsIGludGVyYWN0Lm1vZGVsKQpwX3ZhbHVlX3BvbHkgPC0gY29tcGFyZS5wb2x5JGBQcig+RilgWzJdCmBgYAoKPGRpdj4KVGhlIEFJQyBmb3IgdGhpcyBtb2RlbCB3YXMgYHIgcm91bmQoQUlDNClgLCBtdWNoIGxlc3MgdGhhbiB0aGUgcHJldmlvdXMgQUlDJ3MuIEFOT1ZBIHRlc3QgdGVsbHMgdXMgYWRkaW5nIGBhZ2VeMmAgY2FuIHN0YXRpc3RpY2FsbHkgY2hhbmdlIG91ciBtb2RlbCAsIEYgPSBgciByb3VuZChjb21wYXJlLnBvbHkkRlsyXSwyKWAsIHAtdmFsdWUgPSBgciBmb3JtYXQocF92YWx1ZV9wb2x5LCBkaWdpdHMgPSAyKWAuIEZyb20gdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgcmVzaWR1YWxzLCB3ZSBjYW4gc2VlIHRoYXQgd2UgaGF2ZSBubyBtb3JlIGNvbmRpdGlvbmFsIGhldGVyb3NrZWRhc3RpY2l0eS4gSW4gdGhlIHJlc2lkdWFsIHZzIGxldmVyYWdlIHBsb3QsIG9ubHkgb2JzZXJ2YXRpb24gTm8uIDk5IGhhcyBhIHN0YW5kYXJkIHJlc2lkdWFsIGdyZWF0ZXIgdGhhbiAzLCBjb25zaWRlcmVkIGFuIG91dGxpZXIuIEluIHRoaXMgcGxvdCwgdGhlIGRvdHRlZCByZWQgbGluZXMgYXJlIENvb2vigJlzIGRpc3RhbmNlLCBhbmQgdGhlIGFyZWFzIG9mIGludGVyZXN0IGZvciB1cyBhcmUgdGhlIG9uZXMgb3V0c2lkZSB0aGUgZG90dGVkIGxpbmUgb24gdGhlIHRvcCByaWdodCBjb3JuZXIgKGFuZCBwb3NzaWJseSB0aGUgbG93ZXIgcmlnaHQpLiBUaGUgTm8uIDk5IG9ic2VydmF0aW9uIGlzIGNsb3NlIHRvIHRoaXMgZG90dGVkIHJlZCBsaW5lLgo8L2Rpdj4KCmBgYHtyfQpwYXIobWZyb3c9YygyLDIpKQpwbG90KHBvbHkubW9kZWwpCgpgYGAKCjxkaXY+ClRoZSBiZWxvdyBncmFwaCBzaG93cyB0aGUgYXZlcmFnZSBpbW1pZ3JhbnRzJyBhdmVyYWdlIHNhbGFyaWVzIGJ5IGFnZSBhbmQgZ2VuZGVyLiBQb2ludHMgYXJlIHRoZSBhY3R1YWwgYXZlcmFnZWQgaW5jb21lIGRhdGEsIGFuZCB0aGUgbGluZXMgYXJlIHRoZSBwb2x5bm9taWFsIG1vZGVsIHRoYXQgSSBoYXZlIGJ1aWx0LiBJdCBpcyBmYXNjaW5hdGluZyB0byBzZWUgdGhpcyBkYXRhIGZvbGxvd2VkIHRoZSBtb2RlbCB2ZXJ5IHNtb290aGx5Lgo8L2Rpdj4KCmBgYHtyfQpuZXcuZGYgPC0gZGF0YS5mcmFtZShnZW5kZXIgPSBhdmcuaW5jb21lLmRhdGEkZ2VuZGVyLCBhZ2UgPSBhdmcuaW5jb21lLmRhdGEkYWdlKQpuZXcuZGYkcHJlZC5pbmMgPC0gcHJlZGljdChvYmplY3QgPSBwb2x5Lm1vZGVsLCBuZXdkYXRhID0gbmV3LmRmKQpuZXcuZGYgJT4lIAogIGdncGxvdCgpICsKICBhZXMoYWdlLCBwcmVkLmluYywgY29sb3IgPSBnZW5kZXIpICsgCiAgZ2VvbV9saW5lKCkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGF2Zy5pbmNvbWUuZGF0YSwgYWVzKHggPSBhZ2UsIHkgPSBgQXZlcmFnZSBpbmNvbWVgLCBjb2xvciA9IGdlbmRlcikpCgpgYGAKCgojIyBNdWx0aXBsZSBsb2dpc3RpYyByZWdyZXNzaW9uCgojIyMgY2hpLXNxdWFyZSB0ZXN0CmBgYHtyfQpsaWJyYXJ5KHdlaWdodHMpCgpoZWFsdGhjYXJlMSA8LSBtYWluLmRhdGEgJT4lIAogIHNlbGVjdCh3ZWlnaHQsIGJpcnRoX2NvbnRpbmVudCwgaGVhbHRoX2luc3VyYW5jZSkKCmNoaS5oZWFsdGggPC0gd3RkLmNoaS5zcSh2YXIxID0gaGVhbHRoY2FyZTEkYmlydGhfY29udGluZW50LCB2YXIyID0gaGVhbHRoY2FyZTEkaGVhbHRoX2luc3VyYW5jZSwgd2VpZ2h0ID0gaGVhbHRoY2FyZTEkd2VpZ2h0LCBtZWFuMT1GKQpgYGAKCjxkaXY+CkkgZXhhbWluZWQgdGhlIHdlaWdodGVkIGNoaS1zcXVhcmVkIHRlc3QgdG8gc2VlIGFueSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgdHlwZSBvZiBoZWFsdGggaW5zdXJhbmNlIGFuZCB0aGUgY29udGluZW50IHdoZXJlIGltbWlncmFudHMgd2VyZSBib3JuLiBUaGUgcmVzdWx0IHNob3dlZCBhIHNpZ25pZmljYW50IGRpZmZlcmVuY2UgYmV0d2VlbiB3aGVyZSB0aGV5IHdlcmUgYm9ybiBhbmQgaGVhbHRoIGluc3VyYW5jZS4gQ2hpLnNxID0gYHIgYXMubnVtZXJpYyhjaGkuaGVhbHRoWzFdKWAgYW5kIHAtdmFsdWUgPSBgciBhcy5udW1lcmljKGNoaS5oZWFsdGhbM10pYC4KPC9kaXY+CgojIyMgQ29tcGxleCBtb2RlbApgYGB7cn0KIyBwcmVwYXJpbmcgZGF0YQpiaWcubW9kZWwgPC0gbWFpbi5kYXRhICU+JSAKICBzZWxlY3Qod2VpZ2h0LCBiaXJ0aF9jb250aW5lbnQsICBnZW5kZXIsIGFnZSwgZWR1Y2F0aW9uLCBpbmR1c3RyeSwgCiAgICAgICAgIHdvcmtlZF9wZXJfd2VlaywgYWRqX2luY29tZSwgaGVhbHRoX2luc3VyYW5jZSkgCmBgYAoKPGRpdj4KVG8gaW52ZXN0aWdhdGUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSB0eXBlIG9mIGhlYWx0aCBpbnN1cmFuY2UgYW5kIG90aGVyIHJlbGV2YW50IHZhcmlhYmxlcyBpbiB0aGlzIGRhdGFzZXQuIEkgZ2VuZXJhdGVkIGEgbXVsdGlwbGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBhbmQgaW5jbHVkZWQgYGJpcnRoX2NvbnRpbmVudGAsICBgZ2VuZGVyYCwgYGFnZWAsIGBlZHVjYXRpb25gLCBgaW5kdXN0cnlgLCBgd29ya2VkX3Blcl93ZWVrYCBhbmQgYGFkal9pbmNvbWVgIHRvIGRlc2NyaWJlIHRoZSBgaGVhbHRoX2luc3VyYW5jZWAgdHlwZS4gSSBjb25zaWRlcmVkIHdlaWdodGVkIGRhdGEgaW4gbXkgYW5hbHlzaXMgdG8gZ2V0IGFuIGFjdHVhbCByZXN1bHQuIEl0IGlzIHdvcnRoIG1lbnRpb25pbmcgdGhhdCBJIGRpZCBub3QgY29uc2lkZXIgYGluY29tZV90b19wb3ZlcnR5X3JhdGlvYCBzaW5jZSBpdCBoYXMgYSBwb3NpdGl2ZSBjb3JyZWxhdGlvbiByID0gYHIgcm91bmQoY29yLm1hdFsiQWRqdXN0ZWQgaW5jb21lIiwiSW5jb21lIHRvIHBvdmVydHkgcmF0aW8iXSoxMDApLzEwMGAgd2l0aCBgYWRqX2luY29tZWAsIGFuZCBpdCBtaWdodCBjYXVzZSBjb2xsaW5lYXJpdHkuCjwvZGl2PgoKYGBge3J9CiMgc3VtbWFyaXppbmcgZGF0YQoKa2FibGUoc3VtbWFyeShiaWcubW9kZWwpLCBjYXB0aW9uID0gIkxvZ2lzdGljIHJlZ3Jlc3Npb24gdmFyaWFibGVzIikKYGBgCgojIyMjIERlYWxpbmcgd2l0aCBOQXMKYGBge3J9CiMgRmlsdGVyZWQgb3V0IG1pc3Npbmcgb2JzZXJ2YXRpb25zCmJpZy5tb2RlbCA8LSBiaWcubW9kZWwgJT4lIAogIGZpbHRlcighaXMubmEod29ya2VkX3Blcl93ZWVrKSwgIWlzLm5hKGluZHVzdHJ5KSkKYGBgCgo8ZGl2PgpUbyBnZXQgYSByZWxldmFudCByZXN1bHQsIEkgcmVtb3ZlZCBvYnNlcnZhdGlvbnMgd2l0aCBtaXNzaW5nIHZhbHVlcy4gCjwvZGl2PgoKIyMjIyBUcmFpbiBhbmQgVGVzdCBkYXRhc2V0Cgo8ZGl2PgpUbyBjaGVjayB0aGUgYWNjdXJhY3kgb2YgbXkgbW9kZWwsIEkgZGl2aWRlZCB0aGUgZGF0YSBpbnRvIHRyYWluIGFuZCB0ZXN0IGRhdGFzZXRzIHdpdGggYSA3MDozMCByYXRpbywgcmVzcGVjdGl2ZWx5LiBUaGUgdGFibGVzIGJlbG93IHNob3cgdGhlIHByb3BvcnRpb24gb2YgZWFjaCBkYXRhc2V0LCBpbmRpY2F0aW5nIHRoYXQgdGhvc2UgcmFuZG9tIHNhbXBsaW5ncyBoYXZlIHRoZSBzYW1lIHByb3BvcnRpb24gZm9yIGVhY2ggdHlwZSBvZiBoZWFsdGggaW5zdXJhbmNlLgo8L2Rpdj4KCgpgYGB7cn0KYmlnLm1vZGVsIDwtIGJpZy5tb2RlbCAlPiUgCiAgbXV0YXRlKGlkID0gcm93X251bWJlcigpKQp0cmFpbjEuZGF0YSA8LSBiaWcubW9kZWwgJT4lIHNhbXBsZV9mcmFjKC43MCkKdGVzdDEuZGF0YSA8LWFudGlfam9pbihiaWcubW9kZWwsIHRyYWluMS5kYXRhLCBieT0naWQnKQoKdHJhaW4gPC0gcHJvcC50YWJsZSh3dGQudGFibGUodHJhaW4xLmRhdGEkaGVhbHRoX2luc3VyYW5jZSwgd2VpZ2h0cyA9IHRyYWluMS5kYXRhJHdlaWdodCwgdHlwZSA9ICJ0YWJsZSIpKQprYWJsZSh0cmFpbiwgY29sLm5hbWVzID0gInByb3BvcnRpb24iLCBjYXB0aW9uID0gIlRyYWluIGRhdGFzZXQgcHJvcG9ydGlvbiIsIGZvcm1hdCA9ICJtYXJrZG93biIpCnRlc3QgPC0gcHJvcC50YWJsZSh3dGQudGFibGUodGVzdDEuZGF0YSRoZWFsdGhfaW5zdXJhbmNlLCB3ZWlnaHRzID0gdGVzdDEuZGF0YSR3ZWlnaHQsIHR5cGUgPSAidGFibGUiKSkKa2FibGUodGVzdCwgY29sLm5hbWVzID0gInByb3BvcnRpb24iLCBjYXB0aW9uID0gIlRlc3QgZGF0YXNldCBwcm9wb3J0aW9uIiwgZm9ybWF0ID0gIm1hcmtkb3duIikKCmBgYAoKIyMjIyBCdWlsZCB0aGUgY29tcGxleCBtb2RlbApgYGB7ciBjYWNoZSA9IFRSVUV9CnRpbWUxIDwtIHN5c3RlbS50aW1lKG11bHRpbm9tLmZpdDEgPC0gbXVsdGlub20oaGVhbHRoX2luc3VyYW5jZSB+IC4gLWlkIC13ZWlnaHQsIG1heGl0PTUwMCwgZGF0YSA9IHRyYWluMS5kYXRhLCB3ZWlnaHRzID0gd2VpZ2h0KSkKYGBgCgoKCgpgYGB7cn0KcDEuYmlnPC1wcmVkaWN0KG11bHRpbm9tLmZpdDEsIHR5cGU9ImNsYXNzIiwgbmV3ZGF0YT10ZXN0MS5kYXRhKQpldmFsLmJpZy5tb2RlbDwtIHBvc3RSZXNhbXBsZSh0ZXN0MS5kYXRhJGhlYWx0aF9pbnN1cmFuY2UsIHAxLmJpZykKYGBgCgoKPGRpdj4KYGVkdWNhdGlvbmAgYW5kIGBpbmR1c3RyeWAgaGFkIGByIGxlbmd0aChsZXZlbHMoYmlnLm1vZGVsJGVkdWNhdGlvbikpYCBhbmQgYHIgbGVuZ3RoKGxldmVscyhiaWcubW9kZWwkaW5kdXN0cnkpKWAgdmFsdWVzLCByZXNwZWN0aXZlbHkuIFIgdG9vayB0b28gbXVjaCB0aW1lICh0PWByIHJvdW5kKGFzLm51bWVyaWModGltZTFbMV0pKWBzKSB0byBnZW5lcmF0ZSB0aGUgbW9kZWwuIGByIG11bHRpbm9tLmZpdDEkblsxXS0xYCBkaWZmZXJlbnQgdmFyaWFibGVzIG1hZGUgdGhlIG1vZGVsIGNvbXBsZXguIEhvd2V2ZXIsIHRoZSBtb2RlbCBhY2N1cmFjeSB3YXMgYHIgcm91bmQoYXMubnVtZXJpYyhldmFsLmJpZy5tb2RlbFsxXSkqMTAwLDIpLzEwMGA7IHRoZSBtb2RlbCBlZmZpY2llbmN5IHdhcyBub3QgZ29vZCBlbm91Z2ggYmVjYXVzZSBpdCBoYWQgdG9vIG1hbnkgdmFyaWFibGVzIGFuZCB0b29rIHNldmVyYWwgbWludXRlcyB0byBidWlsZCB0aGUgbW9kZWwuIEluIGFkZGl0aW9uLCB0aGUgaW1wb3J0YW5jZSBvZiBlYWNoIHZhcmlhYmxlIGlzIHNob3duIGluIHRoZSB0YWJsZSBiZWxvdy4KPC9kaXY+CgoKYGBge3J9CiMgVGhlIGltcG9ydGFuY2Ugb2YgZGlmZmVyZW50IHByZWRpY3RvcnMKaW1wLmJpZy5tb2RlbCA8LSB2YXJJbXAobXVsdGlub20uZml0MSkKaW1wLmJpZy5tb2RlbCA8LSBpbXAuYmlnLm1vZGVsICU+JSAKICBhcnJhbmdlKGRlc2MoT3ZlcmFsbCkpCmthYmxlKGltcC5iaWcubW9kZWwsIGRpZ2l0cyA9IDIpCmBgYAoKCiMjIyBDbHVzdGVyaW5nIGVkdWNhdGlvbiBiYXNlZCBvbiBzYWxhcnkKIyMjIyBIaWVyYXJjaGljYWwgY2x1c3RlcmluZwpgYGB7cn0KIyBDbHVzdGVyaW5nIEVkdWNhdGlvbgppbmNfZWR1IDwtIG1haW4uZGF0YSAlPiUgCiAgc2VsZWN0KGVkdWNhdGlvbiwgd2VpZ2h0LCB5ZWFyLCBhZGpfaW5jb21lKSAlPiUgCiAgZ3JvdXBfYnkoZWR1Y2F0aW9uLCB5ZWFyKSAlPiUgCiAgc3VtbWFyaXNlKGFkal9pbmNvbWUgPSB3dGQubWVhbihhZGpfaW5jb21lLCB3ZWlnaHRzID0gd2VpZ2h0LCBub3Jtd3QgPSBGKSkgJT4lIAogIHNwcmVhZChrZXkgPSB5ZWFyLCB2YWx1ZSA9IGFkal9pbmNvbWUpCgprYWJsZShpbmNfZWR1LCBkaWdpdHMgPSAwKQpgYGAKCjxkaXY+CkluIHRoZSBhYm92ZSB0YWJsZSwgSSB3ZWlnaHRlZCBhdmVyYWdlIHRoZSBzYWxhcmllcyBiYXNlZCBvbiB0aGUgeWVhciBhbmQgZWR1Y2F0aW9uLiBUaGVuLCBJIHVzZWQgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgdG8gc2ltcGxpZnkgdGhlIG1vZGVsIHRvIGJ1aWxkIGEgZGVuZHJvZ3JhbSBvZiBlZHVjYXRpb24gYmFzZWQgb24gdGhlIGltbWlncmFudCdzIHllYXJseSBhdmVyYWdlIHNhbGFyaWVzLiBJIHByb3Bvc2VkIGNsdXN0ZXJzIHVzaW5nIGEgaGVpZ2h0IG9mIDQwLDAwMC4KPC9kaXY+CgpgYGB7cn0KdGVtcCA8LSBpbmNfZWR1JGVkdWNhdGlvbgoKcm93Lm5hbWVzKGluY19lZHUpIDwtIGluY19lZHUkZWR1Y2F0aW9uCgoKIyBDYWxjdWxhdGUgRXVjbGlkZWFuIGRpc3RhbmNlIGJldHdlZW4gdGhlIGVkdWNhdGlvbgpkaXN0X2luY19lZHUgPC0gZGlzdChpbmNfZWR1LCBtZXRob2QgPSAiZXVjbGlkZWFuIikKCiMgR2VuZXJhdGUgYW4gYXZlcmFnZSBsaW5rYWdlIGFuYWx5c2lzIApoY19pbmNfZWR1IDwtIGhjbHVzdChkaXN0X2luY19lZHUsIG1ldGhvZCA9ICJhdmVyYWdlIikKCiMgQ3JlYXRlIGEgZGVuZHJvZ3JhbSBvYmplY3QgZnJvbSB0aGUgaGNsdXN0IHZhcmlhYmxlCmRlbmRfaW5jX2VkdSA8LSBhcy5kZW5kcm9ncmFtKGhjX2luY19lZHUpCgojIENvbG9yIGJyYW5jaGVzIGJ5IGNsdXN0ZXIgZm9ybWVkIGZyb20gdGhlIGN1dCBhdCBhIGhlaWdodCBvZiA0MDAwMApkZW5kX2NvbG9yZWQgPC0gY29sb3JfYnJhbmNoZXMoZGVuZF9pbmNfZWR1LCBoID0gNDAwMDApCgojIFBsb3QgdGhlIGNvbG9yZWQgZGVuZHJvZ3JhbQpwbG90KGRlbmRfY29sb3JlZCkgCnRpdGxlKG1haW4gPSAiRWR1Y2F0aW9uIGRlbmRyb2dyYW0iLAogICAgICB5bGFiID0gIkF2ZXJhZ2Ugc2FsYXJ5IikKCgoKCmBgYAoKCgojIyMjIEstbWVhbnMKPGRpdj4KVG8gZmluZCB0aGUgYmVzdCBrIGluIHRoZSBrLW1lYW5zIG1ldGhvZCwgSSBleGFtaW5lZCB0d28gYW5hbHlzZXMsIEVsYm93IGFuYWx5c2lzIGFuZCBBdmVyYWdlIFNpbGhvdWV0dGUgV2lkdGhzLiBLIHdpdGggdGhlIGhpZ2hlc3QgQXZlcmFnZSBTaWxob3VldHRlIFdpZHRocyBzaG91bGQgYmUgY2hvc2VuLiBGcm9tIEVsYm93IGFuYWx5c2lzLCB0aGUgYmVzdCBrIGlzIDQsIGFuZCBBdmVyYWdlIFNpbGhvdWV0dGUgV2lkdGhzIHN1Z2dlc3RlZCB0aGUgaz01IGFzIHRoZSBiZXN0IGsuIE1vcmVvdmVyLCBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyByZXN1bHRlZCBpbiA0IGNsdXN0ZXJzLiBCYXNlZCBvbiB0aGUgY29udGV4dCBvZiB0aGlzIHN0dWR5LCBJIHdvcmtlZCB3aXRoIDQgZ3JvdXBzIHRvIGhhdmUgYSBiZXR0ZXIgb3V0cHV0Lgo8L2Rpdj4KCgoKYGBge3J9CiMjIyMjIyBFbGJvdyBhbmFseXNpcwppbmNfZWR1X3RyaW0gPC0gaW5jX2VkdSAlPiUgCiAgdW5ncm91cChlZHVjYXRpb24pICU+JSAKICBzZWxlY3QoLWVkdWNhdGlvbikKCnJvdy5uYW1lcyhpbmNfZWR1X3RyaW0pIDwtIHRlbXAKCiMgVXNlIG1hcF9kYmwgdG8gcnVuIG1hbnkgbW9kZWxzIHdpdGggdmFyeWluZyB2YWx1ZSBvZiBrIChjZW50ZXJzKQp0b3Rfd2l0aGluc3MgPC0gbWFwX2RibCgxOjEwLCAgZnVuY3Rpb24oayl7CiAgbW9kZWwgPC0ga21lYW5zKHggPSBpbmNfZWR1X3RyaW0sIGNlbnRlcnMgPSBrKQogIG1vZGVsJHRvdC53aXRoaW5zcwp9KQoKIyBHZW5lcmF0ZSBhIGRhdGEgZnJhbWUgY29udGFpbmluZyBib3RoIGsgYW5kIHRvdF93aXRoaW5zcwplbGJvd19kZiA8LSBkYXRhLmZyYW1lKAogIGsgPSAxOjEwLAogIHRvdF93aXRoaW5zcyA9IHRvdF93aXRoaW5zcwopCgojIFBsb3QgdGhlIGVsYm93IHBsb3QKZ2dwbG90KGVsYm93X2RmLCBhZXMoeCA9IGssIHkgPSB0b3Rfd2l0aGluc3MpKSArCiAgZ2VvbV9saW5lKCkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAxOjEwKSArCiAgbGFicyh0aXRsZSA9ICJFbGJvdyBhbmFseXNpcyIpCgpgYGAKCgoKCmBgYHtyfQojIyMjIyMgQXZlcmFnZSBTaWxob3VldHRlIFdpZHRocwojIFVzZSBtYXBfZGJsIHRvIHJ1biBtYW55IG1vZGVscyB3aXRoIHZhcnlpbmcgdmFsdWUgb2YgawpzaWxfd2lkdGggPC0gbWFwX2RibCgyOjEwLCAgZnVuY3Rpb24oayl7CiAgbW9kZWwgPC0gcGFtKGluY19lZHVfdHJpbSwgayA9IGspCiAgbW9kZWwkc2lsaW5mbyRhdmcud2lkdGgKfSkKCiMgR2VuZXJhdGUgYSBkYXRhIGZyYW1lIGNvbnRhaW5pbmcgYm90aCBrIGFuZCBzaWxfd2lkdGgKc2lsX2RmIDwtIGRhdGEuZnJhbWUoCiAgayA9IDI6MTAsCiAgc2lsX3dpZHRoID0gc2lsX3dpZHRoCikKCiMgUGxvdCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gayBhbmQgc2lsX3dpZHRoCmdncGxvdChzaWxfZGYsIGFlcyh4ID0gaywgeSA9IHNpbF93aWR0aCkpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IDI6MTApICsKICBsYWJzKHRpdGxlID0gIkF2ZXJhZ2UgU2lsaG91ZXR0ZSBXaWR0aHMiKQoKYGBgCgpgYGB7cn0Ka19tZWFuc19lZHUgPC0gZGF0YS5mcmFtZShtZXRob2QgPSBjKCJIaWVyYXJjaGljYWwgY2x1c3RlcmluZyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFbGJvdyBhbmFseXNpcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBdmVyYWdlIFNpbGhvdWV0dGUgV2lkdGhzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrID0gYyg0LDQsNSkpCmthYmxlKGtfbWVhbnNfZWR1LCBjYXB0aW9uID0gIkJlc3Qgaz8iKQpgYGAKCgo8ZGl2PgpIZXJlIGFyZSB0aGUgZ3JvdXBzIGZvciBlZHVjYXRpb24gYmFzZWQgb24gdGhlIGF2ZXJhZ2Ugc2FsYXJpZXMgb2YgaW1taWdyYW50cyBiZXR3ZWVuIDIwMTUtMjAxOS4gCjwvZGl2PgoKYGBge3J9CgojIENyZWF0ZSBhIGNsdXN0ZXIgYXNzaWdubWVudCB2ZWN0b3IgYXQgaCA9IDQwLDAwMApjdXRfaW5jX2VkdSA8LSBjdXRyZWUoaGNfaW5jX2VkdSwgaCA9IDQwMDAwKQoKIyBHZW5lcmF0ZSB0aGUgc2VnbWVudGVkIHRoZSBpbmNfZWR1IGRhdGEgZnJhbWUKY2x1c3RfaW5jX2VkdSA8LSBpbmNfZWR1ICU+JSAKICB1bmdyb3VwKGVkdWNhdGlvbikgJT4lIAogIG11dGF0ZShjbHVzdGVyID0gY3V0X2luY19lZHUpCgojIENyZWF0ZSBhIHRpZHkgZGF0YSBmcmFtZSBieSBnYXRoZXJpbmcgdGhlIHllYXIgYW5kIHZhbHVlcyBpbnRvIHR3byBjb2x1bW5zCmdhdGhlcmVkX2luY19lZHUgPC0gZ2F0aGVyKGRhdGEgPSBjbHVzdF9pbmNfZWR1LCAKICAgICAgICAgICAgICAgICAgICAgICBrZXkgPSB5ZWFyLCAKICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IG1lYW5fc2FsYXJ5LCAKICAgICAgICAgICAgICAgICAgICAgICAtZWR1Y2F0aW9uLCAtY2x1c3RlcikKCiMgTmFtZSBlYWNoIGNsdXN0ZXIgYnkgZGVzY2VuZGluZyBhdmVyYWdlIHNhbGFyeSBCYXRjaDEgdG8gQmF0Y2g0CmdhdGhlcmVkX2luY19lZHUkY2x1c3RlciA8LSBmYWN0b3IoZ2F0aGVyZWRfaW5jX2VkdSRjbHVzdGVyLCBsYWJlbHMgPSBjKCJCYXRjaDEiLCJCYXRjaDMiLCJCYXRjaDQiLCJCYXRjaDIiKSkKCmNsdXN0ZXJfZWR1IDwtIGdhdGhlcmVkX2luY19lZHUgJT4lIAogIGZpbHRlcih5ZWFyID09IDIwMTUpICU+JSAKICBzZWxlY3QoZWR1Y2F0aW9uLCBjbHVzdGVyKSAlPiUgCiAgYXJyYW5nZShjbHVzdGVyKQoKa2FibGUoY2x1c3Rlcl9lZHUpCgojIFBsb3QgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG1lYW5fc2FsYXJ5IGFuZCB5ZWFyIGFuZCBjb2xvciB0aGUgbGluZXMgYnkgdGhlIGFzc2lnbmVkIGNsdXN0ZXIKZ2dwbG90KGdhdGhlcmVkX2luY19lZHUsIGFlcyh4ID0geWVhciwgeSA9IG1lYW5fc2FsYXJ5LCBjb2xvciA9IGZjdF9yZW9yZGVyMihjbHVzdGVyLCB5ZWFyLCBtZWFuX3NhbGFyeSkpKSArCiAgICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gZWR1Y2F0aW9uKSkgKwogIGxhYnModGl0bGUgPSAiQ2x1c3RlcmluZyB0aGUgZWR1Y2F0aW9uIHZhcmlhYmxlIiwgY29sb3IgPSAiU2FsYXJ5IikgKwogIHlsYWIoIkF2ZXJhZ2UgU2FsYXJ5IikgCgpgYGAKCgoKIyMjIENsdXN0ZXJpbmcgaW5kdXN0cnkgYmFzZWQgb24gc2FsYXJ5CiMjIyMgSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcKCjxkaXY+CkkgZGlkIHRoZSBleGFjdCBzYW1lIHN0ZXBzIGZvciB0aGUgYGluZHVzdHJ5YCB2YXJpYWJsZS4KPC9kaXY+CgpgYGB7cn0KIyMgQ2x1c3RlcmluZyBJbmR1c3RyeQppbmNfaW5kIDwtIG1haW4uZGF0YSAlPiUgCiAgZmlsdGVyKCFpcy5uYShpbmR1c3RyeSksICFpbmR1c3RyeSA9PSAiVW5lbXBsb3llZCBpbiA1IHllYXJzIikgJT4lIAogIHNlbGVjdChpbmR1c3RyeSwgd2VpZ2h0LCB5ZWFyLCBhZGpfaW5jb21lKSAlPiUgCiAgZ3JvdXBfYnkoaW5kdXN0cnksIHllYXIpICU+JSAKICBzdW1tYXJpc2UoYWRqX2luY29tZSA9IHd0ZC5tZWFuKGFkal9pbmNvbWUsIHdlaWdodHMgPSB3ZWlnaHQsIG5vcm13dCA9IEYpKSAlPiUgCiAgc3ByZWFkKGtleSA9IHllYXIsIHZhbHVlID0gYWRqX2luY29tZSApCgprYWJsZShpbmNfaW5kLCBkaWdpdHMgPSAwKQpgYGAKCjxkaXY+CkluIHRoZSBhYm92ZSB0YWJsZSwgSSB3ZWlnaHRlZCBhdmVyYWdlIHRoZSBzYWxhcmllcyBiYXNlZCBvbiB0aGUgeWVhciBhbmQgaW5kdXN0cnkuIFRoZW4sIEkgdXNlZCBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyB0byBzaW1wbGlmeSB0aGUgbW9kZWwgdG8gYnVpbGQgYSBkZW5kcm9ncmFtIG9mIGluZHVzdHJ5IGJhc2VkIG9uIHRoZSBpbW1pZ3JhbnQncyB5ZWFybHkgYXZlcmFnZSBzYWxhcmllcy4gSSBwcm9wb3NlZCBjbHVzdGVycyB1c2luZyBhIGhlaWdodCBvZiA0MCwwMDAuCjwvZGl2PgoKYGBge3J9CnRlbXAgPC0gaW5jX2luZCRpbmR1c3RyeQoKcm93Lm5hbWVzKGluY19pbmQpIDwtIGluY19pbmQkaW5kdXN0cnkKCgojIENhbGN1bGF0ZSBFdWNsaWRlYW4gZGlzdGFuY2UgYmV0d2VlbiB0aGUgb2NjdXBhdGlvbnMKZGlzdF9pbmNfaW5kdXN0cnkgPC0gZGlzdChpbmNfaW5kLCBtZXRob2QgPSAiZXVjbGlkZWFuIikKCiMgR2VuZXJhdGUgYW4gYXZlcmFnZSBsaW5rYWdlIGFuYWx5c2lzIApoY19pbmNfaW5kdXN0cnkgPC0gaGNsdXN0KGRpc3RfaW5jX2luZHVzdHJ5LCBtZXRob2QgPSAiYXZlcmFnZSIpCgojIENyZWF0ZSBhIGRlbmRyb2dyYW0gb2JqZWN0IGZyb20gdGhlIGhjbHVzdCB2YXJpYWJsZQpkZW5kX2luY19pbmR1c3RyeSA8LSBhcy5kZW5kcm9ncmFtKGhjX2luY19pbmR1c3RyeSkKCiMgQ29sb3IgYnJhbmNoZXMgYnkgY2x1c3RlciBmb3JtZWQgZnJvbSB0aGUgY3V0IGF0IGEgaGVpZ2h0IG9mIDQwMDAwCmRlbmRfY29sb3JlZCA8LSBjb2xvcl9icmFuY2hlcyhkZW5kX2luY19pbmR1c3RyeSwgaCA9IDQwMDAwKQoKIyBQbG90IHRoZSBjb2xvcmVkIGRlbmRyb2dyYW0KcGxvdChkZW5kX2NvbG9yZWQpCnRpdGxlKG1haW4gPSAiSW5kdXN0cnkgZGVuZHJvZ3JhbSIsCiAgICAgIHlsYWIgPSAiQXZlcmFnZSBzYWxhcnkiKQpgYGAKCiMjIyMgSy1tZWFucwoKYGBge3J9CiMjIyMjIEVsYm93IGFuYWx5c2lzCmluY19pbmRfdHJpbSA8LSBpbmNfaW5kICU+JSAKICB1bmdyb3VwKGluZHVzdHJ5KSAlPiUgCiAgc2VsZWN0KC1pbmR1c3RyeSkKCnJvdy5uYW1lcyhpbmNfaW5kX3RyaW0pIDwtIHRlbXAKCiMgVXNlIG1hcF9kYmwgdG8gcnVuIG1hbnkgbW9kZWxzIHdpdGggdmFyeWluZyB2YWx1ZSBvZiBrIChjZW50ZXJzKQp0b3Rfd2l0aGluc3MgPC0gbWFwX2RibCgxOjEwLCAgZnVuY3Rpb24oayl7CiAgbW9kZWwgPC0ga21lYW5zKHggPSBpbmNfaW5kX3RyaW0sIGNlbnRlcnMgPSBrKQogIG1vZGVsJHRvdC53aXRoaW5zcwp9KQoKIyBHZW5lcmF0ZSBhIGRhdGEgZnJhbWUgY29udGFpbmluZyBib3RoIGsgYW5kIHRvdF93aXRoaW5zcwplbGJvd19kZiA8LSBkYXRhLmZyYW1lKAogIGsgPSAxOjEwLAogIHRvdF93aXRoaW5zcyA9IHRvdF93aXRoaW5zcwopCgojIFBsb3QgdGhlIGVsYm93IHBsb3QKZ2dwbG90KGVsYm93X2RmLCBhZXMoeCA9IGssIHkgPSB0b3Rfd2l0aGluc3MpKSArCiAgZ2VvbV9saW5lKCkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAxOjEwKSArCiAgbGFicyh0aXRsZSA9ICJFbGJvdyBhbmFseXNpcyIpCmBgYAoKCgoKYGBge3J9CiMjIyMjIyBBdmVyYWdlIFNpbGhvdWV0dGUgV2lkdGhzCiMgVXNlIG1hcF9kYmwgdG8gcnVuIG1hbnkgbW9kZWxzIHdpdGggdmFyeWluZyB2YWx1ZSBvZiBrCnNpbF93aWR0aCA8LSBtYXBfZGJsKDI6MTAsICBmdW5jdGlvbihrKXsKICBtb2RlbCA8LSBwYW0oaW5jX2luZF90cmltLCBrID0gaykKICBtb2RlbCRzaWxpbmZvJGF2Zy53aWR0aAp9KQoKIyBHZW5lcmF0ZSBhIGRhdGEgZnJhbWUgY29udGFpbmluZyBib3RoIGsgYW5kIHNpbF93aWR0aApzaWxfZGYgPC0gZGF0YS5mcmFtZSgKICBrID0gMjoxMCwKICBzaWxfd2lkdGggPSBzaWxfd2lkdGgKKQoKIyBQbG90IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBrIGFuZCBzaWxfd2lkdGgKZ2dwbG90KHNpbF9kZiwgYWVzKHggPSBrLCB5ID0gc2lsX3dpZHRoKSkgKwogIGdlb21fbGluZSgpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMjoxMCkgKwogIGxhYnModGl0bGUgPSAiQXZlcmFnZSBTaWxob3VldHRlIFdpZHRocyIpCgpgYGAKCmBgYHtyfQprX21lYW5zX2luZCA8LSBkYXRhLmZyYW1lKG1ldGhvZCA9IGMoIkhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkVsYm93IGFuYWx5c2lzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkF2ZXJhZ2UgU2lsaG91ZXR0ZSBXaWR0aHMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGsgPSBjKDMsMywyKSkKa2FibGUoa19tZWFuc19pbmQsIGNhcHRpb24gPSAiQmVzdCBrPyIpCmBgYAo8ZGl2PgpCYXNlZCBvbiB0aGUgY29udGV4dCBvZiB0aGlzIHN0dWR5LCBJIHdvcmtlZCB3aXRoIDMgZ3JvdXBzIHRvIGhhdmUgYSBiZXR0ZXIgb3V0cHV0Lgo8L2Rpdj4KCjxkaXY+CkhlcmUgYXJlIHRoZSBncm91cHMgZm9yIGluZHVzdHJ5IGJhc2VkIG9uIHRoZSBhdmVyYWdlIHNhbGFyaWVzIG9mIGltbWlncmFudHMgYmV0d2VlbiAyMDE1LTIwMTkuCjwvZGl2PgoKYGBge3J9CgojIENyZWF0ZSBhIGNsdXN0ZXIgYXNzaWdubWVudCB2ZWN0b3IgYXQgaCA9IDQwLDAwMApjdXRfaW5jX2luZHVzdHJ5IDwtIGN1dHJlZShoY19pbmNfaW5kdXN0cnksIGggPSA0MDAwMCkKCiMgR2VuZXJhdGUgdGhlIHNlZ21lbnRlZCBjbHVzdF9pbmNfaW5kdXN0cnkgZGF0YSBmcmFtZQpjbHVzdF9pbmNfaW5kdXN0cnkgPC0gaW5jX2luZCAlPiUgCiAgdW5ncm91cChpbmR1c3RyeSkgJT4lIAogIG11dGF0ZShjbHVzdGVyID0gY3V0X2luY19pbmR1c3RyeSkKCiMgQ3JlYXRlIGEgdGlkeSBkYXRhIGZyYW1lIGJ5IGdhdGhlcmluZyB0aGUgeWVhciBhbmQgdmFsdWVzIGludG8gdHdvIGNvbHVtbnMKZ2F0aGVyZWRfaW5jX2luZHVzdHJ5IDwtIGdhdGhlcihkYXRhID0gY2x1c3RfaW5jX2luZHVzdHJ5LCAKICAgICAgICAgICAgICAgICAgICAgICBrZXkgPSB5ZWFyLCAKICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IG1lYW5fc2FsYXJ5LCAKICAgICAgICAgICAgICAgICAgICAgICAtaW5kdXN0cnksIC1jbHVzdGVyKQoKIyBOYW1lIGVhY2ggY2x1c3RlciBieSBkZXNjZW5kaW5nIGF2ZXJhZ2Ugc2FsYXJ5IEJhdGNoMSB0byBCYXRjaDMKZ2F0aGVyZWRfaW5jX2luZHVzdHJ5JGNsdXN0ZXIgPC0gZmFjdG9yKGdhdGhlcmVkX2luY19pbmR1c3RyeSRjbHVzdGVyLCBsYWJlbHMgPSBjKCJCYXRjaDMiLCJCYXRjaDEiLCJCYXRjaDIiKSkKCmNsdXN0ZXJfaW5kIDwtIGdhdGhlcmVkX2luY19pbmR1c3RyeSAlPiUgCiAgZmlsdGVyKHllYXIgPT0gMjAxNSkgJT4lIAogIHNlbGVjdChpbmR1c3RyeSwgY2x1c3RlcikgJT4lIAogIGFycmFuZ2UoY2x1c3RlcikKCmthYmxlKGNsdXN0ZXJfaW5kKQoKIyBQbG90IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBtZWFuX3NhbGFyeSBhbmQgeWVhciBhbmQgY29sb3IgdGhlIGxpbmVzIGJ5IHRoZSBhc3NpZ25lZCBjbHVzdGVyCmdncGxvdChnYXRoZXJlZF9pbmNfaW5kdXN0cnksIGFlcyh4ID0geWVhciwgeSA9IG1lYW5fc2FsYXJ5LCBjb2xvciA9IGZjdF9yZW9yZGVyMihjbHVzdGVyLCB5ZWFyLCBtZWFuX3NhbGFyeSkpKSArIAogICAgZ2VvbV9saW5lKGFlcyhncm91cCA9IGluZHVzdHJ5KSkgKwogIGxhYnModGl0bGUgPSAiQ2x1c3RlcmluZyB0aGUgaW5kdXN0cnkgdmFyaWFibGUiLCBjb2xvciA9ICJTYWxhcnkiKSArCiAgeWxhYigiQXZlcmFnZSBTYWxhcnkiKSAKYGBgCgojIyMgU2ltcGxlIG1vZGVsCmBgYHtyfQojIHByZXBhcmluZyBkYXRhCm1vZGVsLmRhdGEgPC0gbWFpbi5kYXRhICU+JSAKICBzZWxlY3Qod2VpZ2h0LCBiaXJ0aF9jb250aW5lbnQsICBnZW5kZXIsIGFnZSwgZWR1Y2F0aW9uLCBpbmR1c3RyeSwgCiAgICAgICAgIHdvcmtlZF9wZXJfd2VlaywgYWRqX2luY29tZSwgaGVhbHRoX2luc3VyYW5jZSkgCgpgYGAKCgpgYGB7cn0KIyMjIGJpbmRpbmcgZWR1Y2F0aW9uIGludG8gNCBjbHVzdGVycwpCYXRjaDFfZWR1IDwtIGdhdGhlcmVkX2luY19lZHUgJT4lIAogIGZpbHRlcihjbHVzdGVyID09ICJCYXRjaDEiLCB5ZWFyID09IDIwMTUpICU+JSAKICBzZWxlY3QoZWR1Y2F0aW9uKSAlPiUgCiAgcHVsbCgpCgpCYXRjaDJfZWR1IDwtIGdhdGhlcmVkX2luY19lZHUgJT4lIAogIGZpbHRlcihjbHVzdGVyID09ICJCYXRjaDIiLCB5ZWFyID09IDIwMTUpICU+JSAKICBzZWxlY3QoZWR1Y2F0aW9uKSAlPiUgCiAgcHVsbCgpCgpCYXRjaDNfZWR1IDwtIGdhdGhlcmVkX2luY19lZHUgJT4lIAogIGZpbHRlcihjbHVzdGVyID09ICJCYXRjaDMiLCB5ZWFyID09IDIwMTUpICU+JSAKICBzZWxlY3QoZWR1Y2F0aW9uKSAlPiUgCiAgcHVsbCgpCgpCYXRjaDRfZWR1IDwtIGdhdGhlcmVkX2luY19lZHUgJT4lIAogIGZpbHRlcihjbHVzdGVyID09ICJCYXRjaDQiLCB5ZWFyID09IDIwMTUpICU+JSAKICBzZWxlY3QoZWR1Y2F0aW9uKSAlPiUgCiAgcHVsbCgpCgoKbW9kZWwuZGF0YSA8LSBtb2RlbC5kYXRhICU+JSAKICBtdXRhdGUoZWR1Y2F0aW9uID0gZmN0X2NvbGxhcHNlKGVkdWNhdGlvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCYXRjaDEiID0gYXMuY2hhcmFjdGVyKEJhdGNoMV9lZHUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkJhdGNoMiIgPSBhcy5jaGFyYWN0ZXIoQmF0Y2gyX2VkdSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQmF0Y2gzIiA9IGFzLmNoYXJhY3RlcihCYXRjaDNfZWR1KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkJhdGNoNCIgPSBhcy5jaGFyYWN0ZXIoQmF0Y2g0X2VkdSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkKYGBgCgoKYGBge3J9CiMjIyBiaW5kaW5nIGluZHVzdHJ5IHRvIDMgY2x1c3RlcnMKQmF0Y2gxX2luZCA8LSBnYXRoZXJlZF9pbmNfaW5kdXN0cnkgJT4lIAogIGZpbHRlcihjbHVzdGVyID09ICJCYXRjaDEiLCB5ZWFyID09IDIwMTUpICU+JSAKICBzZWxlY3QoaW5kdXN0cnkpICU+JSAKICBwdWxsKCkKCkJhdGNoMl9pbmQgPC0gZ2F0aGVyZWRfaW5jX2luZHVzdHJ5ICU+JSAKICBmaWx0ZXIoY2x1c3RlciA9PSAiQmF0Y2gyIiwgeWVhciA9PSAyMDE1KSAlPiUgCiAgc2VsZWN0KGluZHVzdHJ5KSAlPiUgCiAgcHVsbCgpCgpCYXRjaDNfaW5kIDwtIGdhdGhlcmVkX2luY19pbmR1c3RyeSAlPiUgCiAgZmlsdGVyKGNsdXN0ZXIgPT0gIkJhdGNoMyIsIHllYXIgPT0gMjAxNSkgJT4lIAogIHNlbGVjdChpbmR1c3RyeSkgJT4lIAogIHB1bGwoKQoKCm1vZGVsLmRhdGEgPC0gbW9kZWwuZGF0YSAlPiUgCiAgbXV0YXRlKGluZHVzdHJ5ID0gZmN0X2NvbGxhcHNlKGluZHVzdHJ5LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkJhdGNoMSIgPSBhcy5jaGFyYWN0ZXIoQmF0Y2gxX2luZCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQmF0Y2gyIiA9IGFzLmNoYXJhY3RlcihCYXRjaDJfaW5kKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCYXRjaDMiID0gYXMuY2hhcmFjdGVyKEJhdGNoM19pbmQpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpIApgYGAKCjxkaXY+CkkgYnVpbHQgdGhlIHNhbWUgbW9kZWwgb2YgbXVsdGlwbGUgbG9naXN0aWMgcmVncmVzc2lvbi4gSG93ZXZlciwgSSB1c2VkIHRoZSBjbHVzdGVyIGFuYWx5c2lzIGZyb20gdGhlIHByZXZpb3VzIHBhcnRzIHRvIGdyb3VwIHRoZSBgZWR1Y2F0aW9uYCBhbmQgYGluZHVzdHJ5YCB2YXJpYWJsZXMuIFVzaW5nIHRoYXQgbWFkZSB0aGUgbnVtYmVyIG9mIHZhcmlhYmxlcyBsZXNzIGFuZCBjYWxjdWxhdGlvbiBtb3JlIGVmZmljaWVudC4gIAo8L2Rpdj4KCiMjIyMgU3VtbWFyeSBhbmQgcmVtb3Zpbmcgd2l0aCBOQXMKCjxkaXY+CkFzIHNlZW4gZnJvbSB0aGUgZGF0YSBzdW1tYXJ5LCBzb21lIG9ic2VydmF0aW9ucyBoYXZlIG1pc3NpbmcgdmFsdWVzLiBNb3Jlb3ZlciwgSSBkcm9wcGVkIHRoZSBgZWR1Y2F0aW9uYCBhbmQgYGluZHVzdHJ5YCBsZXZlbCBvbmx5IHRvIGNvbnNpZGVyIGJhdGNoZXMuCjwvZGl2PgoKYGBge3J9CiMgc3VtbWFyaXppbmcgZGF0YQprYWJsZShzdW1tYXJ5KG1vZGVsLmRhdGEpLCBjYXB0aW9uID0gIkxvZ2lzdGljIHJlZ3Jlc3Npb24gdmFyaWFibGVzIikKCiMgcmVtb3ZlIE5Bcwptb2RlbC5kYXRhIDwtIG1vZGVsLmRhdGEgJT4lIAogIGZpbHRlcighaXMubmEod29ya2VkX3Blcl93ZWVrKSwgIWlzLm5hKGluZHVzdHJ5KSkKCiMgcmVtb3ZlIHRoZSB1bnVzZWQgZmFjdG9yIGxldmVscwptb2RlbC5kYXRhJGVkdWNhdGlvbiA8LSBkcm9wbGV2ZWxzKG1vZGVsLmRhdGEkZWR1Y2F0aW9uKQptb2RlbC5kYXRhJGluZHVzdHJ5IDwtIGRyb3BsZXZlbHMobW9kZWwuZGF0YSRpbmR1c3RyeSkKYGBgCgojIyMjIFRyYWluIGFuZCBUZXN0IGRhdGFzZXQKCjxkaXY+CkkgZGlkIHRoZSBzYW1lIGFzIHRoZSBjb21wbGV4IG1vZGVsIGFuZCBkaXZpZGVkIHRoZSBkYXRhIGludG8gdHJhaW4gYW5kIHRlc3QgZGF0YXNldHMgd2l0aCBhIDcwOjMwIHJhdGlvLiBUaGUgdGFibGVzIGJlbG93IHNob3cgdGhlIHByb3BvcnRpb24gb2YgZWFjaCBkYXRhc2V0LCBpbmRpY2F0aW5nIHRoYXQgdGhvc2UgcmFuZG9tIHNhbXBsaW5ncyBoYXZlIHRoZSBzYW1lIHJhdGlvIGZvciBlYWNoIHR5cGUgb2YgaGVhbHRoIGluc3VyYW5jZS4KPC9kaXY+CgpgYGB7cn0KbW9kZWwuZGF0YSA8LSBtb2RlbC5kYXRhICU+JSAKICBtdXRhdGUoaWQgPSByb3dfbnVtYmVyKCkpCnRyYWluLmRhdGEgPC0gbW9kZWwuZGF0YSAlPiUgc2FtcGxlX2ZyYWMoLjcwKQp0ZXN0LmRhdGEgPC1hbnRpX2pvaW4obW9kZWwuZGF0YSwgdHJhaW4uZGF0YSwgYnk9J2lkJykKCnRyYWluLnNpbXBsZSA8LSBwcm9wLnRhYmxlKHd0ZC50YWJsZSh0cmFpbi5kYXRhJGhlYWx0aF9pbnN1cmFuY2UsIHdlaWdodHMgPSB0cmFpbi5kYXRhJHdlaWdodCwgdHlwZSA9ICJ0YWJsZSIpKQoKa2FibGUodHJhaW4uc2ltcGxlLCBjb2wubmFtZXMgPSAicHJvcG9ydGlvbiIsIGNhcHRpb24gPSAiVHJhaW4gZGF0YXNldCBwcm9wb3J0aW9uIiwgZm9ybWF0ID0gIm1hcmtkb3duIikKCnRlc3Quc2ltcGxlIDwtIHByb3AudGFibGUod3RkLnRhYmxlKHRlc3QuZGF0YSRoZWFsdGhfaW5zdXJhbmNlLCB3ZWlnaHRzID0gdGVzdC5kYXRhJHdlaWdodCwgdHlwZSA9ICJ0YWJsZSIpKQoKa2FibGUodGVzdC5zaW1wbGUsIGNvbC5uYW1lcyA9ICJwcm9wb3J0aW9uIiwgY2FwdGlvbiA9ICJUZXN0IGRhdGFzZXQgcHJvcG9ydGlvbiIsIGZvcm1hdCA9ICJtYXJrZG93biIpCgoKYGBgCgojIyMjIEJ1aWxkIHRoZSBzaW1wbGUgbW9kZWwKYGBge3IgY2FjaGUgPSBUUlVFfQp0aW1lMiA8LSBzeXN0ZW0udGltZShtdWx0aW5vbS5maXQgPC0gbXVsdGlub20oaGVhbHRoX2luc3VyYW5jZSB+IC4gLWlkIC13ZWlnaHQsIG1heGl0PTUwMCwgZGF0YSA9IHRyYWluLmRhdGEsIHdlaWdodHMgPSB3ZWlnaHQpKQpgYGAKCmBgYHtyfQpwMTwtcHJlZGljdChtdWx0aW5vbS5maXQsIHR5cGU9ImNsYXNzIiwgbmV3ZGF0YT10ZXN0LmRhdGEpCmV2YWwubW9kZWw8LSBwb3N0UmVzYW1wbGUodGVzdC5kYXRhJGhlYWx0aF9pbnN1cmFuY2UsIHAxKQpgYGAKCjxkaXY+ClRoaXMgbW9kZWwgaGFzIGByIG11bHRpbm9tLmZpdCRuWzFdLTFgIGRpZmZlcmVudCB2YXJpYWJsZXMsIGxlc3MgdGhhbiBvbmUtdGhpcmQgb2YgdGhlIGNvbXBsZXggbW9kZWwuIFRoZSBtb2RlbCdzIGFjY3VyYWN5IHdhcyBgciByb3VuZChhcy5udW1lcmljKGV2YWwubW9kZWxbMV0pKjEwMCwyKS8xMDBgLCBvbmx5IGByIGFicyhyb3VuZChhcy5udW1lcmljKGV2YWwuYmlnLm1vZGVsWzFdKSoxMDAsMikvMTAwLXJvdW5kKGFzLm51bWVyaWMoZXZhbC5tb2RlbFsxXSkqMTAwLDIpLzEwMClgIGxvd2VyIHRoYW4gdGhlIGludHJpY2F0ZSBtb2RlbC4gVGhlIG1vZGVsIGVmZmljaWVuY3kgd2FzIHByZXR0eSBnb29kOyBpdCB0b29rIGByIHJvdW5kKGFzLm51bWVyaWModGltZTJbMV0pKWAgc2Vjb25kcyB0byBjcmVhdGUgaXQuIE1vcmVvdmVyLCB0aGUgaW1wb3J0YW5jZSBvZiBlYWNoIHZhcmlhYmxlIGlzIHNob3duIGluIHRoZSB0YWJsZSBiZWxvdy4gSW1taWdyYW50cyB3aG8gaGF2ZSBiZWVuIGxpdmluZyBpbiB0aGUgU291dGggQW1lcmljYW4gY29udGluZW50IGhhdmUgdGhlIG1vc3QgaW1wYWN0IG9uIHByZWRpY3RpbmcgdGhlIHR5cGUgb2YgaGVhbHRoIGluc3VyYW5jZSwgZm9sbG93ZWQgYnkgaW5kdXN0cmllcyBhbGxvY2F0ZWQgdG8gYmF0Y2ggMi4KPC9kaXY+CgpgYGB7cn0KIyBUaGUgaW1wb3J0YW5jZSBvZiBkaWZmZXJlbnQgcHJlZGljdG9ycwppbXAubW9kZWwgPC0gdmFySW1wKG11bHRpbm9tLmZpdCkKaW1wLm1vZGVsIDwtIGltcC5tb2RlbCAlPiUgCiAgYXJyYW5nZShkZXNjKE92ZXJhbGwpKQprYWJsZShpbXAubW9kZWwsIGRpZ2l0cyA9IDIpCmBgYAojIERpc2N1c3Npb24KCjxkaXY+CkhlcmUgaXMgdGhlIHN1bW1hcnkgb2YgbXkgZmlyc3QgcmVncmVzc2lvbiBtb2RlbC4gVGhlIGNvbXBhcmlzb24gdGFibGUgc2hvd3MgdGhhdCB0aGUgbm9ubGluZWFyIG1vZGVsIGdhdmUgdGhlIGJlc3QgcmVzdWx0LiBJIGF2ZXJhZ2VkIGFsbCB0aGUgaW1taWdyYW50cycgc2FsYXJpZXMgYmFzZWQgb24gYWdlIGFuZCBnZW5kZXIgaW4gdGhpcyByZXNlYXJjaC4gVGhlcmVmb3JlLCB0aGlzIG1vZGVsIGlzIG5vdCBhcHByb3ByaWF0ZSB0byBwcmVkaWN0IHRoZSBuZXcgZGF0YS4gSXQgb25seSBnaXZlcyB3aGF0IHRoZSBhdmVyYWdlIG9mIHRoaXMgY29tbXVuaXR5IHdvdWxkIGJlIGlmIHdlIGhhdmUgdGhlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBhZ2UgYW5kIGdlbmRlciBvZiBhbiBpbW1pZ3JhbnQgaW4gdGhlIFVTQS4gVGhpcyBtb2RlbCBpcyBwb3RlbnQgdG8gdGVzdCBpbiBvdGhlciBjb21tdW5pdGllcyBzaW5jZSB0aGlzIGRhdGEgaGFzIGEgbGFyZ2Ugc2FtcGxlIHNpemUgYW5kIGNhbiBiZSBnZW5lcmFsaXplZC4KPC9kaXY+CmBgYHtyfQpgQWRqdXN0ZWQgUi1zcXVhcmVkYCA9IGMocm91bmQoc3VtbWFyeS5hZ2UkYWRqLnIuc3F1YXJlZCoxMDAsMikvMTAwLAogICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoc3VtbWFyeS5nZW5kZXIkYWRqLnIuc3F1YXJlZCoxMDAsMikvMTAwLAogICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoc3VtbWFyeS5pbnRlcmFjdCRhZGouci5zcXVhcmVkKjEwMCwyKS8xMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChwb2x5Lm1vZGVsLnN1bW1hcnkkYWRqLnIuc3F1YXJlZCoxMDAsMikvMTAwKQoKQUlDID0gYyhBSUMxLCBBSUMyLCBBSUMzLCBBSUM0KQoKSXNzdWVzID0gYygiQ29uZC4gSGV0LCBOb24tbm9ybWFsaXR5LCBWZXJ5IExvdyBBZGp1c3RlZCBSLXNxdWFyZWQiLCAKICAgICAgICAgICAiQ29uZC4gSGV0LiIsIAogICAgICAgICAgICJDb25kLiBIZXQuLCBMb3dlciBBZGp1c3RlZCBSLXNxdWFyZWQiLCAKICAgICAgICAgICAiLSIpCmNvbXBhcmUucmVncmVzc2lvbiA8LSBkYXRhLmZyYW1lKEFJQywgYEFkanVzdGVkIFItc3F1YXJlZGAsIElzc3VlcykKCnJvdy5uYW1lcyhjb21wYXJlLnJlZ3Jlc3Npb24pIDwtIGMoIm9ubHkgYWdlIiwgImFnZSAmIGdlbmRlciIsICJpbnRlcmFjdGlvbiIsICJub24tbGluZWFyIikKCmthYmxlKGNvbXBhcmUucmVncmVzc2lvbiwgY2FwdGlvbiA9ICJDb21wYXJpbmcgcmVncmVzc2lvbiBtb2RlbHMgb2YgZXhwbGFpbmluZyBzYWxhcnkiLCBhbGlnbiA9ICJjIikKYGBgCgo8ZGl2PgpUaGUgZWZmaWNpZW5jeSBhbmQgYWNjdXJhY3kgb2YgbXVsdGlwbGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMgYXJlIGNvbXBhcmVkIGJlbG93LiBBcyBpdCBjYW4gYmUgc2VlbiwgdGhlIHNpbXBsZSBtb2RlbCBpcyB3YXkgYmV0dGVyIHRoYW4gdGhlIGNvbXBsZXggb25lLiBHZW5lcmFsbHksIGFjY3VyYWN5IGJldHdlZW4gMC42Mi0wLjc1IGlzIHByZXR0eSBnb29kIGZvciBtdWx0aXBsZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVscy4gVGhlcmVmb3JlLCBJIGFtIGNvbmZpZGVudCBpbiB0aGVzZSBmaW5kaW5ncywgYW5kIHRoaXMgcmVzdWx0IGlzIGhlbHBmdWwgZm9yIGhlYWx0aCBpbnN1cmFuY2UgY29tcGFuaWVzIHRvIHRhcmdldCB3aGljaCBwZW9wbGUgY2FuIGJlIG1hcmtldGVkIGZvciBlaXRoZXIgcHVibGljIG9yIHByaXZhdGUgaGVhbHRoIGluc3VyYW5jZS4gVGhpcyBzdHVkeSBjYW4gYmUgZnVydGhlciBpbnZlc3RpZ2F0ZWQgdXNpbmcgb3RoZXIgbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWVzIHN1Y2ggYXMgcmFuZG9tIGZvcmVzdCBhbmQgZGVjaXNpb24gdHJlZXMgdG8gc2VlIHdoaWNoIG1vZGVsIGdpdmVzIHVzIGJldHRlciByZXN1bHRzLgo8L2Rpdj4KCmBgYHtyfQpBY2N1cmFjeSA9IGMocm91bmQoYXMubnVtZXJpYyhldmFsLmJpZy5tb2RlbFsxXSkqMTAwLDIpLzEwMCwKICAgICAgICAgICAgIHJvdW5kKGFzLm51bWVyaWMoZXZhbC5tb2RlbFsxXSkqMTAwLDIpLzEwMCkKCkFJQy5sb2dpc3RpYyA9IGMobXVsdGlub20uZml0MSRBSUMsIG11bHRpbm9tLmZpdCRBSUMpCgp0aW1lLmxvZ2lzdGljID0gYyhyb3VuZChhcy5udW1lcmljKHRpbWUxWzFdKSksIHJvdW5kKGFzLm51bWVyaWModGltZTJbMV0pKSkKCmBObyBvZiB2YXJpYWJsZXNgID0gYyhtdWx0aW5vbS5maXQxJG5bMV0tMSwgbXVsdGlub20uZml0JG5bMV0tMSkKY29tcGFyZS5sb2dpc3RpYy5yZWdyZXNzaW9uIDwtIGRhdGEuZnJhbWUoQWNjdXJhY3ksIEFJQy5sb2dpc3RpYywgdGltZS5sb2dpc3RpYywgYE5vIG9mIHZhcmlhYmxlc2ApCgpyb3cubmFtZXMoY29tcGFyZS5sb2dpc3RpYy5yZWdyZXNzaW9uKSA8LSBjKCJDb21wbGV4IG1vZGVsIiwgIlNpbXBsZSBtb2RlbCIpCgpjb2xuYW1lcyhjb21wYXJlLmxvZ2lzdGljLnJlZ3Jlc3Npb24pIDwtIGMoIkFjY3VyYWN5IiwgIkFJQyIsICJUaW1lIHRvIHNvbHZlIiwgIk5vIG9mIHZhcmlhYmxlcyIpCmthYmxlKGNvbXBhcmUubG9naXN0aWMucmVncmVzc2lvbiwgY2FwdGlvbiA9ICJDb21wYXJpbmcgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMgb2YgcHJlZGljdGluZyBoZWFsdGggaW5zdXJhbmNlIHR5cGUiLCBhbGlnbiA9ICJjIikKYGBgCjxkaXY+Ck15IHN0dWR5IGhhcyBsaW1pdGF0aW9ucywgc3VjaCBhcyB0b3AtY29kZWQgdmFyaWFibGUgYGFkal9pbmNvbWVgIG9yIGZpbHRlcmluZyB0aGUgYGFnZWAgYmV0d2VlbiAyMCBhbmQgNzAuIEFsc28sIHRoZSBkYXRhIGlzIHNhbXBsZWQgYW5kIHdlaWdodGVkIGJ5IHNvbWUgc3RhdGlzdGljYWwgdG9vbHMgdG8gZGVtb25zdHJhdGUgdGhlIHdob2xlIHBvcHVsYXRpb24sIHdoaWNoIG1pZ2h0IGJlIGluY29ycmVjdCBhbmQgaW5mbHVlbmNlIHRoZSByZXN1bHQgb2YgdGhpcyBzdHVkeS4KCjwvZGl2Pgo=